A list that keeps growing between function calls is one of the most common Python surprises in exams and interviews. Here is why it happens, with output I ran on Python 3.10.
The bug
def add_bad(x, bucket=[]):
bucket.append(x)
return bucket
print(add_bad(1))
print(add_bad(2))
print(add_bad(3))
Output:
[1]
[1, 2]
[1, 2, 3]
You expected [1], [2], [3]. Python builds the default value once, when def runs, not on each call. Every call that skips bucket reuses the same list. You can see it:
print(add_bad.__defaults__) # ([1, 2, 3],)
The fix
Use None as the default and create the list inside the function.
def add_ok(x, bucket=None):
if bucket is None:
bucket = []
bucket.append(x)
return bucket
print(add_ok(1)) # [1]
print(add_ok(2)) # [2]
Same family: shallow copies
list.copy() copies only the outer list. The inner lists are shared.
import copy
a = [[1, 2], [3, 4]]
b = a.copy()
b[0].append(99)
print(a) # [[1, 2, 99], [3, 4]] a changed too
c = copy.deepcopy(a)
c[0].append(5)
print(a, c) # a is unchanged by c
Same family: the grid trap
grid = [[0] * 3] * 2
grid[0][0] = 7
print(grid) # [[7, 0, 0], [7, 0, 0]] both rows are one list
grid2 = [[0] * 3 for _ in range(2)]
grid2[0][0] = 7
print(grid2) # [[7, 0, 0], [0, 0, 0]]
[x] * n repeats a reference to the same object. A list comprehension builds a new row each time.
Quick rule
- Never use a list, dict or set as a default argument. Use
None. -
copy()and slicing are shallow. Usecopy.deepcopyfor nested data. - Build 2D grids with a comprehension, not
*.
Try to predict the output of each snippet before you run it. That habit is what exam questions test.
I wrote a longer version with 7 more tested examples and exam-style questions here: Python Mutable Default Arguments and Shallow Copies. It is part of TechVani, a free BTech CSE notes and lab-files site.
Top comments (0)