Wednesday, October 15, 2008

Beware Mutable default value initializers in Python

As a new Python programmer I was surprised by the behavior of default parameter expressions. I knew they were only executed once when the function is first defined, but I hadn't realized the ramifications for using a static dictionary object as a default expression. This little gotcha hit me yesterday and took quite a while for me to figure out what was happening. Here's some sample code:

def Bad(dict={}):
    print "Bad: %s" % repr(dict)
    dict['p'] = 1

Bad()
Bad()

Which results in:

Bad: {}
Bad: {'p': 1}

Since the value of dict is mutable, it can be changed as a side effect of the function. So all subsequent calls will use a default value that has been modified by previous calls to the function! This can be (and was) a nightmare to track down in a large program.

I fixed this by changing to the following:

def Good(dict=None):
    if dict == None:
        dict = {}
    print "Good: %s" % repr(dict)
    dict['p'] = 1

Good()
Good()

Which results in:

Good: {}
Good: {}

The fix de-couples the effects of one call on subsequent calls (as was the intention of the original code).

3 comments:

  1. It's usually not a good idea to use built-in function names liked dict as variable names. When checking to see if an object is None, use object identity comparison instead of ==. Use %r to print repr.

    def Good(arg=None):
    if arg is None:
    arg = {}
    print "Good: %r" % dict
    arg["p"] = 1

    ReplyDelete
  2. Thanks! I never use "dict" explicitly, so I forgot that was a Python built-in class - oops!

    I also like your other two tips; thanks for the lesson!

    ReplyDelete
  3. Dict is the most wonderful thing. This

    m = dict(a=1, b=2)

    is easier to read than:

    m = {'a': 1, 'b': 2}

    ReplyDelete