Default arguments in Python: two easy blunders

by garth on May 9, 2008

I’m glad I stumbled across Patrick Altman’s tweet about a “default bug in Django“. I’d never have guessed you can pass a callable to a field’s default= argument, otherwise. That’s quite a powerful idiom, and I think I’ll use it a lot.

To balance the karma, I’d like to post a quick reminder to everyone else that expressions in default arguments are calculated when the function is defined, not when it’s called. In Patrick’s code, for example, all objects created in the same running session got the same timestamp. Try this in the Python interactive prompt:

>>>import time
>>> def report(when=time.time()): 
...     print when
... 
>>> report()
1210294387.19
>>> time.sleep(5)
>>> report()
1210294387.19

Until the interpreter quits, you’ll always get the same timestamp. The correct way to go about this is to default to None or some other sentinel, then replace it inside the function:

>>> def report(when=None): 
...     if when is None:
...         when = time.time()
...     print when
... 
>>> report()
1210294762.29
>>> time.sleep(5)
>>> report()
1210294772.23

Now that you know about that blunder, you should be able to figure out what’s going on with this second classic blunder when using default arguments in Python:

>>> def spam(eggs=[]): 
...     eggs.append("spam")
...     return eggs
... 
>>> spam()
['spam']
>>> spam()
['spam', 'spam']
>>> spam()
['spam', 'spam', 'spam']
>>> spam()
['spam', 'spam', 'spam', 'spam']