Python Language Common Pitfalls Mutable default argument

Help us to keep this website almost Ad Free! It takes only 10 seconds of your time:
> Step 1: Go view our video on YouTube: EF Core Bulk Extensions
> Step 2: And Like the video. BONUS: You can also share it!

Example

def foo(li=[]):
    li.append(1)
    print(li)

foo([2])
# Out: [2, 1]
foo([3])
# Out: [3, 1]

This code behaves as expected, but what if we don't pass an argument?

foo()
# Out: [1] As expected...

foo()
# Out: [1, 1]  Not as expected...

This is because default arguments of functions and methods are evaluated at definition time rather than run time. So we only ever have a single instance of the li list.

The way to get around it is to use only immutable types for default arguments:

def foo(li=None):
    if not li:
        li = []
    li.append(1)
    print(li)

foo()
# Out: [1]

foo()
# Out: [1]

While an improvement and although if not li correctly evaluates to False, many other objects do as well, such as zero-length sequences. The following example arguments can cause unintended results:

x = []
foo(li=x)
# Out: [1]

foo(li="")
# Out: [1]

foo(li=0) 
# Out: [1]

The idiomatic approach is to directly check the argument against the None object:

def foo(li=None):
    if li is None:
        li = []
    li.append(1)
    print(li)

foo()
# Out: [1]


Got any Python Language Question?