So, here I have got a little surprise for you... 😀😀😀
We have a Python function that takes an argument that 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 output 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 bind to the function as soon as the function is defined.
Therefore, we have a single list associated 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. Our function code above imitates is the following code:
a = []
def foo():
a.append(5)
return a
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’s mutable default arguments...A single such object is created when the function is defined.
Let's create another function that 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 proof that default arguments get bind as soon as the 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 don'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 learned 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.