The Surprising Case Of Mutable Default Arguments

Written by h3avren | Published 2022/01/31
Tech Story Tags: python | python-programming | python-tutorials | learn-python | learn-to-code-python | python-basics | python-skills | mutable-default-arguments | web-monetization

TLDRA Python function takes in an argument which also has a default value assigned to it. Default arguments are bind to the function as soon as the function is defined. But default arguments are mutable so we get the same results instead of a new list being created on each function call. And the same list is being mutated/modified again and again. We have a single list and the default argument always modifies the same thing. The default argument is a mutable list which is mutable and that's what is happening here.via the TL;DR App

Surprise..!

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]

What..?

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

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

What's the proof..?

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.

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 don't change.

Conclusion

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.


Written by h3avren | Dreaming with Python... Under a sky in India...
Published by HackerNoon on 2022/01/31