Why do Python iterable and non-iterable keyword argument types behave differently with multiple function calls?

40 Views Asked by At

I was reading this very informative question and answer and learned about this behavior for the first time: calling

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

foo()
foo()
foo([])
foo()

prints

[1]
[1,1]
[1]
[1,1,1]

I thought that was neat and wanted to try it with other variable types as default arguments.

Running

import math
def foo(l=[],bar=0,baz={"z":0},bap="a"):
    l.append(1)
    bar+=1
    baz["z"]+=1
    bap=chr(ord(bap)+1)
    print(locals())

foo()
foo()
foo([],math.pi,{"z":0},"?")
foo()

prints

{'l': [1], 'bar': 1, 'baz': {'z': 1}, 'bap': 'b'}
{'l': [1, 1], 'bar': 1, 'baz': {'z': 2}, 'bap': 'b'}
{'l': [1], 'bar': 4.141592653589793, 'baz': {'z': 1}, 'bap': '@'}
{'l': [1, 1, 1], 'bar': 1, 'baz': {'z': 3}, 'bap': 'b'}

which caught me totally off-guard. I was expecting incrementing the integer bar and string bap to be analogous to appending/modifying elements of l and baz and cause similar behavior, but it didn't - they print the same values each foo call (unless non-default arguments are provided).

Hence the question in the title. I was thinking that the difference was caused by iterable versus non-iterable data types in my example.

1

There are 1 best solutions below

5
DoodleVib On

I learned from another informative post and a helpful link therein that my suspicion was off-target. The issue isn't caused by iterable versus non-iterable types.

The difference in default argument behavior happens because some of those default arguments I chose are mutable (l list, baz dict) and others are immutable (bar int, bap str).