2.1Lists, tuples, dictionaries & sets
The essentials in 30 seconds
- Four containers, four uses: ordered mutable list, frozen ordered tuple, dict by keys, set of unique values — the right container for the right use.
- The dictionary is the queen structure: a list of dicts = 90% of real-world data (API, CSV, JSON) — access by key, .get() with no KeyError.
- Copying is not assigning: = shares, .copy() copies flat, deepcopy() copies everything — verify with a test on the nested part.
Running mini-case — Nabil and the sales in Algiers
Nabil, a junior data analyst in Algiers, receives a sales export: a list of rows with duplicates, customers to look up by code, frozen GPS coordinates and categories to deduplicate. His first version looks up customers by scanning a list — slow and fragile. He rebuilds: sales as a list of dictionaries (one key per field), the customer directory as a dict indexed by code with .get(), coordinates as immutable tuples, categories as a set. This same dataset returns in 2.2 (reports formatted with f-strings, JSON saving), 2.3 (Vente and Client objects) and 2.4 (missing file handled cleanly).
Running thread of level 2: this same Nabil dataset returns in the lesson, the diagram, the script and the 48h challenge — and continues in 2.2, 2.3 and 2.4.
1. Four containers, four temperaments
Real applications handle collections: a list of sales, a customer directory, categories. Python offers four fundamental structures. The list ([]) is ordered and mutable — the Swiss army knife: adding with append(), access by index starting at 0, last element with [-1]. The tuple (()) is ordered but immutable: perfect for frozen data (GPS coordinates, a frozen pair) and usable as a dictionary key. The dictionary ({}) maps keys to values — the address book: access by key, not by position. The set (set()) keeps only unique values, with no order: ideal for deduplicating and testing membership with in. Nabil applies it: sales in a list, coordinates in a tuple, customers in a dict, categories in a set.
2. The dictionary, queen structure
The dictionary deserves its status: client = {"nom": "Nabil", "wilaya": "Algiers"}, access with client["nom"], and above all client.get("tel", "?") which returns a default value with no KeyError. A list of dictionaries represents 90% of real-world data (API, CSV or JSON rows): each row is a dict, the whole is a list. You iterate with .keys() (keys), .values() (values) and .items() (pairs: for cle, valeur in d.items()). Looking up by key in a dict is direct; looking up by key while scanning a list is slow and fragile — Nabil's rule: as soon as you look up by identifier, index into a dict.
3. Copying without sharing (labels strike back)
In Python, b = a does not copy: both names point to the same object — modifying one modifies the other. b = a.copy() copies shallowly (nested objects stay shared), copy.deepcopy() copies everything, including the nested part. Rule: copy() for a flat structure, deepcopy() for nested data — and a small test to verify (id() or a trial modification). Nabil remembers: his "copy" of the monthly table kept modifying the original until he switched to deepcopy for the nested rows.
🔎 Concrete example — 🔎 Concrete example — Deduplicating and counting Nabil's wilayas
The set deduplicates, the comprehension counts, as on Nabil's sales:
ventes = ["Algiers", "Oran", "Algiers", "Blida", "Oran", "Algiers"]
uniques = set(ventes)
comptes = {w: ventes.count(w) for w in uniques}
print(comptes) # {'Algiers': 3, 'Oran': 2, 'Blida': 1}
Reading: set() deduplicates, the dict comprehension counts each wilaya.
Lesson: tuple for immutable composite keys, dict for access by key.
Section vocabulary
- List
- Ordered and mutable collection, in brackets: [1, 2, 3]. Adding with append(), access by index from 0, [-1] for the last item.
Simple example: Nabil stores his sales in a list iterated with for.
What it is not: a key-based access structure.
Reflex: order to walk through = list.
- Tuple
- Ordered but IMMUTABLE (non-modifiable) collection, in parentheses: (lat, lon). Protects frozen data, usable as a dict key.
Simple example: Nabil's GPS coordinates are tuples.
What it is not: a list modified along the program.
Reflex: frozen data = tuple.
- Dictionary (dict)
- Set of key→value pairs, in braces: {"nom": "Nabil"}. Access by key, .get() with a default and no KeyError, iteration with .items().
Simple example: Nabil finds a customer by code via annuaire[code].
What it is not: a list scanned to look up by identifier.
Reflex: looking up by identifier = dict.
- Set
- Unordered collection of UNIQUE values: {1, 2, 3}. Ideal for deduplicating (set(liste)) and testing membership with in.
Simple example: Nabil deduplicates categories with set(categories).
What it is not: a list with duplicates for testing membership.
Reflex: unique values or membership = set.
- Copy (assignment / copy / deepcopy)
- = shares the same object; .copy() copies shallowly (nested part shared); copy.deepcopy() copies everything, including the nested part.
Simple example: Nabil duplicates his nested monthly table with deepcopy.
What it is not: an assignment mistaken for a copy.
Reflex: nested data to duplicate = deepcopy + test.
In practice — In practice — Choosing and handling the right structure
- Create a sales list, add (append), remove (remove), sort (sort) and access by index. Iterate over it with a for loop.
- Create a "client" dictionary (nom, email, wilaya). Access by key, add a key, use .get() with a default, iterate with .items().
- Create a coordinate tuple (immutable) and try to modify it: observe the error. Conclude on the value of immutability.
- Deduplicate a list with set() then count with a dict comprehension. Check the difference between = and .copy() on a list.
Script ready to say — justifying the dict to a colleague (30 s)
"For Nabil's sales, I keep the list for order, but customers are indexed in a dict by code: direct access, .get() with a default instead of crashing, and iteration with .items(). Scanning the list on every lookup was slow and fragile — now it is readable and instant."
Say it while showing a lookup by code before (loop) and after (dict).
Copy-paste template — indexed directory and counting
ventes = [
{"client": "C01", "wilaya": "Algiers", "montant": 1200},
{"client": "C02", "wilaya": "Oran", "montant": 800},
{"client": "C01", "wilaya": "Algiers", "montant": 450},
]
annuaire = {"C01": {"nom": "Nabil", "wilaya": "Algiers"}, "C02": {"nom": "Lina", "wilaya": "Oran"}}
for v in ventes:
client = annuaire.get(v["client"], {"nom": "unknown"})
print(client["nom"], "->", v["montant"])
wilayas = [v["wilaya"] for v in ventes]
print({w: wilayas.count(w) for w in set(wilayas)})
Export to deduplicate
Nabil's sales arrive duplicated: set() on the identifiers, then a cleaned list of dicts.
Directory by code
Each customer has a unique code: dict indexed by code, .get() with a default for unknown codes.
Frozen coordinates
Delivery points are tuples (lat, lon): immutable, usable as dict keys.
Monthly table to duplicate
The nested summary is duplicated with deepcopy: the original from Algiers stays intact.
✓ Best practices
- Index into a dict as soon as you look up by identifier (never scan a list).
- Use .get() with a default and .items() to iterate cleanly.
- Copy consciously: = shares, copy() is flat, deepcopy() is for nested data.
✗ Common pitfalls
- Looking up by key while scanning a list (slow and fragile).
- Using dict[cle] without .get() and suffering a KeyError.
- Believing that = copies the structure (it shares the same object).
Key takeaways
- List = ordered + mutable; tuple = ordered + frozen (immutable).
- Dictionary = key→value pairs (the directory), ubiquitous (JSON, config); .get() avoids KeyError.
- Set = unique values, perfect for deduplicating and testing with in.
- Indices start at 0; [-1] means the last one; = shares, copy()/deepcopy() copy.
48h challenge — apply tomorrow
Take a real export (Nabil's or yours): store it as a list of dicts, index a directory as a dict by code with .get(), deduplicate a column with set(), and prove the = vs .copy() difference on a nested case.
- I can choose the right structure (list, tuple, dict, set) with one example of each.
- I can index into a dict, use .get() and iterate with .items().
- I have verified the difference between assignment, copy() and deepcopy().
Frequently asked questions
When should I use a tuple rather than a list?
Use a tuple for data that must not change (GPS coordinates, a date, a frozen pair). Immutability protects against accidental modification, lets you use the tuple as a dictionary key, and is slightly faster. Anything that evolves (additions, removals, sorting) belongs to lists.
How do I iterate over a dictionary?
Use .items() to get the pairs: "for cle, valeur in mon_dict.items()". You can also iterate over keys only (.keys(), or directly for cle in mon_dict) or values (.values()). For safe access, prefer .get(cle, defaut) over dict[cle] when the key may be missing.
What is the difference between a list and a tuple?