shoyer
shoyer

Reputation: 9603

What is the most Pythonic way to use an empty dictionary as a default argument?

Using default arguments of the form x={} usually does not accomplish the intended purpose in Python, since default arguments are bound when a function is defined, not called.

The convention seems to be to set mutable objects as default arguments with x=None and then check x is None to assign the proper default when the function is called.

So if I would like to cast x to a dictionary that is by default empty, I would use something like the following:

def f(x=None):
    x = dict(x) if x is not None else {}

However, since dict can take any iterable, I could also write this more succinct version:

def f(x=()):
    x = dict(x)

Which of these is the "right" approach?

Upvotes: 7

Views: 2546

Answers (3)

Alfe
Alfe

Reputation: 59516

It needs to be mentioned that an empty dict as an argument is only a problem if the function itself changes this dict. In case the dict is just read, no problem occurs whatsoever.

And if there is a changing of the dict intended, why should an empty dict make sense as a default argument? Since this hasn't been provided by the caller they do not have a reference to this and the changes done to the dict are lost after the call.

The only case in which this might make sense is when the function needs to do something for which a changed version of its input is needed. In such a case the function should probably create the changed version without changing the input directly, for instance:

# bad way:
def f(x={}):
  x['newkey'] = 'newvalue'
  print(x)  # do something with the extended x

# probably better way:
def f(x={}):
  new_dict = {}
  new_dict.update(x)
  new_dict.update({ 'newkey': 'newvalue' })
  print(new_dict)  # do something with the extended x

In this way (the latter one) the original x is never changed, so no problem occurs.

I know that the mutable default argument problem is well-known and accepted to be a problem. But I also have the feeling that running into it is just a symptom of a more profound problem with the structure of the code it appears in and I wanted to point that out in this post. Or put the other way: In a soundly structured piece of code a mutable default argument should never result in an actual problem.

Upvotes: 1

Martijn Pieters
Martijn Pieters

Reputation: 1123500

The idiomatic style is to not cast to dict; that way someone could use any object that implements the correct mapping methods.

So, the most pythonic method is to use:

def f(x=None):
    if x is None:
        x = {}

and then just use mapping methods.

So, you generally should not cast arguments to a dict. You state your API accepts a mapping object instead, and expect callers to do the casting.

The only reason to accept both a dict and an iterable is when you want to support ordered key-value pairs where duplicate keys are allowed, such as for the urllib.urlencode function. In such a case a dict cannot retain that information, and that method does not cast the iterable to a dict, but rather uses the dict as an iterable.

Upvotes: 8

jimhark
jimhark

Reputation: 5046

There's not a right or wrong answer here, but I'd say the first is more idiomatic (that doesn't make the second one wrong, or bad, or worse, etc.)

Upvotes: 1

Related Questions