A tuple is a fixed record
A tuple is immutable, which is why it can be a dictionary key or a set member, and why it is the natural return type for a function that answers more than one thing at once. The parentheses are usually optional: return x, y builds a tuple.
When a tuple grows past three fields, reach for a NamedTuple or a dataclass — person[2] is unreadable two weeks later.
point = (3, 4)
x, y = point # unpacking — this is the point of tuples
a, b = b, a # swap, with no temporary
single = (5,) # the comma makes it a tuple; (5) is just 5
empty = ()
first, *rest = [1, 2, 3, 4] # first=1, rest=[2, 3, 4]
name, _, age = ("ada", "x", 36) # _ is the convention for "not used"
Sets: membership and de-duplication
A set is an unordered collection of unique, hashable values. Membership is constant time rather than a scan, which is the reason to use one: the difference between a script that takes a minute and one that takes an hour.
It has no order. list(set(items)) de-duplicates but scrambles; when order matters use dict.fromkeys(items), which keeps the first occurrence of each.
seen = {"a", "b", "c"} # a set literal
empty = set() # {} is an empty DICT, not a set
seen.add("d")
seen.discard("z") # no error if missing; .remove() raises
"a" in seen # O(1), however big the set gets
list(set(items)) # de-duplicated — but the order is gone
Set algebra reads like the mathematics
"Which users are in both groups", "which files are new since last time", "which permissions is this role missing" — all of them are one operator, and all of them are fast.
a = {1, 2, 3}
b = {3, 4, 5}
a | b # {1, 2, 3, 4, 5} union a.union(b)
a & b # {3} intersection a.intersection(b)
a - b # {1, 2} difference a.difference(b)
a ^ b # {1, 2, 4, 5} symmetric difference
a <= b # is a a subset of b?
a.isdisjoint(b) # nothing in common?
Hashable means usable as a key
A value may go in a set, or be a dictionary key, only if it is hashable — and in practice that means immutable. Lists, dicts and sets are not; numbers, strings, tuples of hashables and frozenset are.
The rule behind it: if a key could change after it was stored, the structure could no longer find it.
{(1, 2), (3, 4)} # fine — tuples are hashable
{[1, 2]} # TypeError: unhashable type: 'list'
frozenset({1, 2}) # an immutable set — this one IS hashable
Choosing
Getting this right once, at the start, is usually worth more than any later optimisation.
| Question | Container |
|---|---|
| Does the order matter, and will it change? | list |
| Is it a fixed record of a few fields? | tuple — or a NamedTuple |
| Do I only need "is this in here?" | set |
| Am I looking things up by a key? | dict |
| Do I keep taking from the front? | collections.deque |