Python · 02/10 6 min
Names, mutability and copies
Variables are labels: aliasing, shallow vs deep copies, is vs ==, and hashability.
Names point at objects
a = [1, 2]
b = a # same list, two names
b.append(3)
a # [1, 2, 3]
Assignment never copies. Passing a list into a function passes the same list: if the function mutates it, the caller sees the change.
def add_tag(tags, t):
tags.append(t) # mutates the caller's list
return tags
def add_tag(tags, t):
return [*tags, t] # returns a new list
Shallow vs deep copy
import copy
orig = {"user": {"name": "a"}, "tags": ["x"]}
shallow = dict(orig) # or orig.copy(), {**orig}
shallow["tags"].append("y") # orig["tags"] changed too: inner objects are shared
deep = copy.deepcopy(orig) # fully independent
list(x), x[:], x.copy() and {**d} are all shallow.
+= on lists mutates
a = [1]; b = a
a += [2] # in place: b is [1, 2] too
a = a + [3] # new list: b unaffected
is vs ==
==compares values.iscompares identity.- Use
isonly forNone(and other singletons):if x is None. a is bfor small ints or short strings sometimes "works" because of caching. Don't rely on it.
Mutable vs immutable
| Immutable | Mutable |
|---|---|
int, float, str, tuple, frozenset, bytes |
list, dict, set, most objects |
Only hashable (immutable) values can be dict keys or set members. A tuple containing a list isn't hashable. Use tuple(sorted(items)) or frozenset(items) as a key.
Class attributes are shared
class Account:
history = [] # ❌ ONE list for every account
def __init__(self):
self.history = [] # ✅ per instance
Practise it now
Next in Python
Iterators, generators and comprehensions