Reference · module 13
What can change
Everything this module explains, on one page.
Mutable and immutable
Tell at a glance whether a call changed the thing or handed you a new one.
- A list can be changed without making a new one: xs.append(4) changes the list itself. A string cannot. It has no method that alters it — every single one returns a new string.
- Mutable: list, dict, set. Immutable: number, string, tuple, True and None. The split runs exactly along whether the type has methods that change it in place.
- So here is a reading rule. A method that returns None probably changed the thing. A method that returns something probably left it alone. The clearest pair is sort and sorted.
The mistake you are about to make
xs += [4] and xs = xs + [4] are the same
xs += [4] changes that very list
For a number or a string += makes a new value, so a list looks like it should behave the same way. It changes the list in place, and therefore for everyone holding it.
Two names, one list
Know when you have a copy and when you have a second name for the same thing.
- b = a does not copy a list. Both names point at the same one, so b.append(4) shows up through a as well. With numbers and strings this never happens: they cannot change, so there is nothing to notice.
- A copy has to be asked for: b = a.copy() or b = list(a). Now there are two lists, and they change separately.
- The is operator asks whether two names hold the same object. The == operator asks whether the contents match. Two copies are equal and not identical.
The mistake you are about to make
b = a (meaning: make a copy)
b = a.copy()
Assignment looks like copying, and for a number it is. For a list it hands out a second name for the same object, and a change through one shows through the other.
A list inside a function
Decide whether your function changes what it was given or hands back something new.
- A function does not get a copy of the list. It gets the same list. So it can change it, and the change is visible outside — even when the function returns nothing at all.
- There are two honest options. Change what you were given and return None, or leave it alone and return something new. Pick one, and let the name of the function say which.
- Doing both is the bad option. A function that changes the list and also returns it reads as "here is a new one", and the caller builds a bug on top of that reading.
The mistake you are about to make
def add(xs): xs.append(1); return xs
def add(xs): xs.append(1)
Returning the changed list feels like a convenience: changed it and handed it back. The reader sees return and assumes the original survived. That is why sort and sorted are separate.
A copy of a copy
See why copying a list of dicts does not separate the dicts.
- a.copy() copies one level. If there are lists or dicts inside, the copy points at those same inner objects, and a change made inside shows through both copies.
- This is what a shallow copy means. For a flat list of numbers it makes no difference at all. For a list of dicts it makes every difference, and the difference tends to surface late.
- A deep copy is copy.deepcopy(x): it walks inside and copies everything it finds. It costs more, so it is for nested data that really does get changed.
The mistake you are about to make
rows.copy() for a list of dicts
copy.deepcopy(rows)
The copy is made, so the data looks separated. Only the outer list was copied: the dicts inside are the same ones, and editing the copy edits the original.
Why immutable is useful
Use a tuple where a list would quietly break things.
- Only something immutable can be a dict key: a string, a number, a tuple. A list cannot be one — it could change while the dict is holding it, and the entry would be lost.
- Immutable things are safe to hand around: nobody can spoil one from a distance. That is why a coordinate, a date or a row of a table is kept as a tuple rather than a list.
- The other side of it: joining strings in a loop makes a new string every time round. For a hundred short ones nobody notices. For a million they do, and there you collect a list and join it once.
The mistake you are about to make
d[[1, 2]] = "x"
d[(1, 2)] = "x"
A pair of numbers looks like a perfectly good key. A key has to be immutable, or it could change while the dict holds it: TypeError, unhashable type.