Dangerous Python Default Arguments

Dangerous? Really? Well, not if you understand how it works.

Note: I’m not the first to write about this subject.

When writing a function in Python, it’s handy to use default argument values like this:

And you think this will provide an easy way to let lazy callers pass no arguments to your function, and it will simply be called as if they had passed ['some item'].

But you would be wrong.

What actually happens is this (interactive shell output):

That’s right. Python creates a new object, named some_list that persists as an attribute of the function. If callers don’t pass their own some_list object, this one, the same one, is used each time your function is called.

Weird, huh?

If users pass their own some_list object, then sanity is restored:

So why is this? Python stores each default argument value in a special attribute on the function called func_defaults. You can inspect any function’s default arguments like this:

Because functions in Python are just objects, you can store arbitrary attributes on them. And indeed, you can actually modify the default function arguments at run time, like this:

But remember: Just because you can doesn’t mean you should. I would not recommend making a habit of stuff like this, especially if you like not having your co-workers hate you.

It’s always good to know how your tools work. Inside and out.

I discovered this behavior when investigating the pylint W0102 message, and discovered that this message actually inspired an entire wiki of Pylint message descriptions.

6 comments to “Dangerous Python Default Arguments”

You can leave a reply or Trackback this post.
  1. This is my first time using Gist. I think I like it, but the WordPress gist plugin seems to do aggressive caching, which makes it hard to edit your gists and see live results on your article while editing.

  2. This seems to violate the principle of least astonishment, which is OK as long as there is a substantial benefit to the alternative behavior. I can see a benefit to the implementer as you already have an object that’s like a big hash table and so you may as well just shove default arguments int the same object as everything else. I can also see how this design keeps python pure from an “it’s all objects” perspective, but I don’t see any advantage to the end developer who’s just trying to write solid bug free code. Are there any good benefits to the end developer for this behavior?

  3. Evan, maybe one advantage is that the function author can use default values as a way to maintain state between function calls. Although, there are more obvious ways to do this that I would prefer for understandability though (“private” module variables, which are described in PEP8).

  4. I had that problem back when I programmed in python. The rule of thumb for my team was. Defaults shouldn’t be used with things that get changed in the function.

  5. My friend Alex pointed out a very egregious usage of this idiom:

    def foo(some_date = datetime.now()):
       ...
    

    Super bad.

    Erin: How about “thou shalt not mutate default argument values”?

  6. Dave: that would work as well. I usually just go with thou shall not use a default