The Mutable Default Argument Mess in Python

Subscribe to my newsletter and never miss my upcoming articles

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)

Looks good, time for testing the solution:

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

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

append()
# OUTPUT: ['#', '#']  | Strange!!
  • 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 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)

Let's test this solution -

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

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

append()
# OUTPUT: ['#']        | Works fine!

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:

Connect with me on Twitter, GitHub, and LinkedIn.

Sunayna Padhye's photo

index.jpg

Yuvraj Singh Jadon's photo

Thanks, Sunayna

Apoorv Tyagi's photo

Nice Article!!