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]

What..?

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...

Why is it that way..?

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.

What's the proof..?

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.

What happens to the non-mutable default arguments..?

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