DEV Community

Cover image for The Mutable Default Argument Mess in Python
Yuvraj Singh Jadon
Yuvraj Singh Jadon

Posted on

The Mutable Default Argument Mess in Python

Meet Kevin, 🙋‍♂️ Kevin is learning Python. One day he was given a problem to solve as follows:

Design a function that appends '#' to a list provided as an argument and then prints it. If no argument is provided then the function should use an empty list as a default.

Kevin quickly comes up with the following solution:

def append(l = []):
    l.append('#')
    print(l)
Enter fullscreen mode Exit fullscreen mode

Looks good, time for testing the solution:

append([1, 2, 3])
# OUTPUT: [1, 2, 3, '#'] | OK

append()
# OUTPUT: ['#'] | OK

append()
# OUTPUT: ['#', '#']  | Strange!!
Enter fullscreen mode Exit fullscreen mode
  • The first call to append worked fine. It added # to the list [1, 2, 3] and then printed it.

  • The second also worked as expected. This time no list is provided as an argument, so it used the default empty list and appended a # to it.

  • Now, the third call resulted in something unexpected.

When we once again called the append without argument, it printed ['#', '#'] instead of ['#'] as we got in the above call.

Why did this happened?

The reason this happened is that Python defines its default argument only once when the function is first defined.

This is because python is parsed line by line, when the parser encounters def it sets the default argument to a value and that value is used in every future call.

This behavior of Python becomes of special concern when the default argument is a mutable.

As the value of , immutables can not be changed, if you update the argument variable inside the function it will create a new object and start pointing to that object instead of changing the original default object.

But in the case of a mutable default argument, the object created at the time of parsing the function is updated instead of creating a different object for that function call.

Solution

The solution to this problem is to use an immutable default argument instead of a mutable. The preferred choice is None (though you can choose any immutable value).

def append(l = None):
    if l is None:
        l = []
    l.append('#')
    print(l)
Enter fullscreen mode Exit fullscreen mode

Let's test this solution -

append([1, 2, 3])
# OUTPUT: [1, 2, 3, '#']    | OK

append()
# OUTPUT: ['#']     | OK

append()
# OUTPUT: ['#']     | Works fine!
Enter fullscreen mode Exit fullscreen mode

Great! this solution worked as expected.

But why? Let's take a look inside...

Why the solution works?

Watch this video to find out what happened in the wrong version of the code -

As you can see, in this case, the original l was modified instead of creating a new l for each function call as a list is a mutable value.

Now, see the corrected version of the code -

Here as None is an immutable value therefore it cannot be changed and a new list object is created for each function call.

Note: The default argument is a property of the function object therefore, initially it is the same for all function calls.

Thanks For Reading 😊

If you found this article helpful, please like and share!
Feedbacks are welcome in the comments.


You may also like:

This article was originally published on Yuvraj's CS Blog.

Connect with me on Twitter, GitHub, and LinkedIn.

Top comments (2)

Collapse
 
andrewdmay profile image
Andrew May

Ah, so that's what that warning in PyCharm that I've been ignoring is about!
Thanks for the useful post.

Collapse
 
yuvraajsj18 profile image
Yuvraj Singh Jadon

Glad you liked it 😊

Happy New Year 🎉