Mutable default arguments are your friend.

In a recent comment to an elderly post of mine, I was asked about the following code:

def mywrapper(func, args=(), kwargs={}):
    ...

The commenter though that I should have made a special mention about using dict as a default argument, “because it’s such a common gotcha.”

My response is twofold:

  1. This particular case is idiomatic, and widely used for functions that call other functions.
  2. I actually don’t think mutable default arguments are a problem and that they don’t deserve all the stigma they are getting.

I want to expand on point 2 a bit here.

A piece of history

As far as I can tell from the internet, this has been an issue for a long, long time. Here is a thread from 2001 talking at length about it.

The stated problem with mutable default arguments is this: If the calling function mutates the argument, but the argument is the default, it is the default that gets mutated and this mutation persist for the next call.

I.e. this code

def foo(arg=[]):
    arg.append("ho")
    print arg()

will produce

>>>foo()
["ho"]
>>>foo()
["ho", "ho"]

The argument goes, that this is confusing, and surprising, and thus, people should steer clear of mutable default arguments since they are a source of unexpected bugs.

Mutable default arguments are widely decried in the community. Tools like pylint shout at you for doing this. Klugscheiße programmers admonish you for doing it. It raises eyebrows, and discussion in every code review.

My argument

In my option the problem with the above code is not the default argument. The problem is that the function is mutating its argument. Modifying the function argument is an unusual thing to do and from the caller’s perspective mostly unexpected.

Most functions don’t mutate their arguments. It’s not very Pythonic. Think about PHP’s array_push() function for a minute and shudder.

If they do, it should be with a clear understanding and consent of both parties.

Continuing this logic: If a function is doing something as unusual as modifying the argument for the caller, then the function should:

  1. Preferably not accept default arguments at all. What’s the point of a mutating function if it doesn’t mutate?
  2. Be carefully written.

The point is:

Most functions don’t modify their arguments. Mutable default arguments are exactly as safe as mutable (non-default) arguments in this case. In the vast majority of use cases they make complete and utter sense.

The onus should be on he who writes a function that is supposed to modify a mutable (non-default) argument to do it properly. This involves not having a default value or using None or something similar.

Conclusion

Default arguments are a beautiful and extremely useful feature of Python. Let’s not spoil the fun by decrying every use of a list or dict as a default and force the common programmer into a state of paranoia and unnecessary boiler plate code. Let’s pull them back into the fold and rather point a stringent finger towards those that write argument-modifying code.

Advertisement

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s