Tuesday, July 5, 2011

Python: Beware of standard parameter values

I just spent an hour hunting for a strange bug in my Python code, and once I found it it made perfect sense (it often does when one is on that side of a bug hunt), but it was still obscure enough that I want to share it with you.

The bug occurred because of my (wrong) use of standard parameter values. Look at the following piece of code
class Foo(object):
def __init__(self, mylist = []):
self._mylist = mylist

def addSomethingToMyList(self, something):
self._mylist.append(something)

def __str__(self):
return str(self._mylist)

if __name__ == '__main__':
f = Foo()
f.addSomethingToMyList(42)
print "I just added 42 to f's mylist, now it contains", f
f2 = Foo()
f2.addSomethingToMyList(117)
print "I just added 117 to f2's mylist, now it contains", f2

Just by looking at it, one could expect that this would print:
I just added 42 to f's mylist, now it contains [42]
I just added 117 to f2's mylist, now it contains [117]

It doesn't though. What it prints is this:
I just added 42 to f's mylist, now it contains [42]
I just added 117 to f2's mylist, now it contains [42, 117]

Now why is that, you may ask? Well it has something to do with when standard parameter values are instantiated - which must be on class definition time. So if you use a standard parameter value that is a reference type (objects, list, dicts, sets, etc) it is that one instance that is passed on to all instances of your class.

The lesson therefore is: never use reference types in standard parameter values!