1

Possible Duplicate:
“Least Astonishment” in Python: The Mutable Default Argument

As I was reading the following handout: http://python.net/~goodger/projects/pycon/2007/idiomatic/handout.html#other-languages-have-variables

I came across the following example. Basically, it claims that:

def bad_append(new_item, a_list=[]):
    a_list.append(new_item)
    return a_list

is not the best way to append items to a list since a_list is evaluated at at function definition time.

Instead, a better alternative is:

def good_append(new_item, a_list=None):
    if a_list is None:
        a_list = []
    a_list.append(new_item)
    return a_list

since it defines the variable in the function's runtime.

Coming from a C-background, isn't a_list a local variable here? How does it store its value from one function call to the other? Furthermore, can someone please elaborate on why the second example is better than the first? What's wrong with defining functions in the definition? It doesn't seem like it overwrites the original value or anything.

Thanks!

Community
  • 1
  • 1
darksky
  • 20,411
  • 61
  • 165
  • 254

1 Answers1

2

A simple answer is that the def statement creates an object, and at the objects execution (when the def line is executed), a new list object is created and attached to the object (in this case, the object is a function).

At subsequent execution - what you might be expecting is that a new list is generated; but in reality the same object that was created when the function definition was first run sticks around; and all "members" of that object remain the same. That is why it keeps adding to the same list.

The reason it is considered bad is because what you might expect from the argument (that at every execution, a new list is generated) is not what the statement does. This is why the better pattern is to pass None or some other blank object.

Consider the following:

>>> bad_append('foo')
['foo']
>>> bad_append('bar')
['foo', 'bar']
>>> bad_append('zoo')
['foo', 'bar', 'zoo']
>>> bad_append('bar',[])
['bar']
>>> bad_append('bar') # what?
['foo', 'bar', 'zoo', 'bar']

As you can see when I passed in a new list, at the next execution it doesn't stick around; but rather the original list that was built when the def was executed remains.

For more information - see this excellent explanation from effbot.

Burhan Khalid
  • 169,990
  • 18
  • 245
  • 284