18
The surprising case of Mutuable default arguments in Python
So, here I have got a little surprise for you... :):):)
We have a Python function that takes in an argument which also has a default value assigned to it.
def foo(a = []):
a.append(5)
return a
Now, if I pass a list to the function it would append 5
to it...
>>> foo([1,2,3,4])
[1,2,3,4,5]
>>> foo([1,2,3,4,5,6])
[1,2,3,4,5,6,5]
But, what happens when we do not pass any argument and prefer to go with the default argument..? Let's see it ourselves:
>>> foo()
[5]
>>> foo()
[5,5]
>>> foo()
[5,5,5]
Wasn't the ouput supposed to be a new list with just 5
in it a single element list which would look like [5]
..?
But this is not what actually happens...
It's cause our default argument is a list
or a mutable datatype i.e. it can be modified and that's what is happening here.
The point to notice is Default arguments are binded to the function as soon as the function is defined therefore, we have a single list assosciated with our function instead of a new list being created on each function call. And the same list is being mutated/modified again and again.The code is the same as the following:
a = []
def foo():
a.append(5)
return
Here, we have a single list and the function always modifies the same list:
>>> foo()
[5]
>>> foo()
[5,5]
>>> foo()
[5,5,5]
So, that's the case with python mutable default arguments...A single such object is created when the function is defined.
Let's create another function which would output a message when executed and return an empty list and then we would use it to initialize our default argument.
>>> def eggs():
print('eggs() executed')
return []
>>> def foo(a = eggs()): # as soon as you hit enter after the definition it outputs 'eggs() executed'
a.append(5)
return a
eggs() executed
Now, that message right after the function definition was a prrof that default arguments are binded as soon as function is defined.
These are also treated the same way i.e. as mutable default arguments these too are bound to the function just after the definition ends.But, as these are immutable so we get the same results instead of modified once.
Let's use the above code again:
>>> def eggs():
print('eggs() executed')
return () # this time we use a tuple instead as these are immutable
>>> def foo(a = eggs()): # as soon as you hit enter after the definition it outputs 'eggs() executed'
a += (5,)
return a
eggs() executed
>>> foo()
(5,)
>>> foo()
(5,)
Now, you see these doesn't change.
You are now aware of default argument bindings to functions in Python so be careful, don't be amazed if you get a modified output instead of what you expected...:):):)
I too learnt this from Stackoverflow.
If you wish to learn further why was it implemented this way or what kind of situations you could be in then just go and read the following stackoverflow answer “Least Astonishment” and the Mutable Default Argument.
18