Reputation: 9603
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
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
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
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