The Python Tourist #1: Passing Mutable Objects as Default Args

Many modern languages allow for default function arguments. Default arguments can be a great thing - they allow you to use sensible defaults for the common cases, without taking away the power to add more functionality as needed. You can arbitrarily expand a function definition without breaking existing code by providing sensible defaults for the new parameters.

Here is a fun example of how Python will bite you if you try to use a mutable object (like a list, dict, or object instance) as a default argument. The following is an example function I'm trying to write ...

# OK, now I am *INTENDING* to write a routine to do the following:
#
#    Append an item to a list, returning the list.
#    If no list is passed, a new list is created.
#
# Here is my first attempt at implementation ...
def add_item(item, the_list = []):    
    the_list.append(item)
    return the_list


At first glance this appears to do what is intended. If the user doesn't pass a list to append items to, then a new list is started. Or at least that appears to be what will happen. Lets try running it:

# I want to start a new list ...
word_list = add_item('First')

# Add some more words
word_list = add_item('Second', word_list)
word_list = add_item('Third', word_list)

# Start a new list of numbers ...
number_list = add_item(111)

# Add some more numbers
number_list = add_item(222, number_list)
number_list = add_item(333, number_list)

# Now lets see what happened ...
print "word_list ",word_list
print "number_list ",number_list


When you run this, you will get the following output:

word_list  ['First', 'Second', 'Third', 111, 222, 333]
number_list  ['First', 'Second', 'Third', 111, 222, 333]


Whoa! That was unexpected. When you use a mutable object as a default arg, Python creates a single object, and reuses that same object on every call. I find this non-intuitive, personally, but that's the way Python works. I think it has something to do with passing args to lambda functions, but I could be completely wrong.

Anyways, here is the correct way to code the function to acheive the result I want:

# fixed version ...
def add_item(item, the_list = None): # declare default as None, then ...

    # NOW, I can set to a new list if no list is passed
    the_list = the_list or []  
    the_list.append(item)
    return the_list


Now when you run the program you will get the expected result:

word_list  ['First', 'Second', 'Third']
number_list  [111, 222, 333]


Note that not every instance of using a mutable type as a default arg will cause problems. In fact, you'll probably be OK most of the time. The danger is in those times that you aren't expecting trouble, and can't figure out why your code isn't working. I find it tends to happen more with recursive functions.

At any rate, my personal rule of thumb is to never use a mutable type as a default arg, and instead to always us None as shown above, and then set the variable inside the function body (where a new empty object will always be created).

It's simple, neat, takes one line, and can save you a marathon debugging session trying to figure out what is wrong (speaking from painful experience!)


Written in WikklyText.

Comments

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.

This was helpful

This hasn't bit me in the butt yet but I'd never had this pointed out to me before. Thanks.

> I think it has something

> I think it has something to do with passing args to lambda functions, but I could be completely wrong.

No, it's because Python evaluates those default arguments when the function is defined, not each time it is run.

Right, but actually I was

Right, but actually I was wondering what the motivation was for that design feature. And I think I read somewhere that they exist for the convenience of lambda functions. But ... I may have that wrong.

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.

Post new comment

The content of this field is kept private and will not be shown publicly.