I put a dict as the default value for an optional argument to a Python function, and pylint (using Sublime package) told me it was dangerous. Can someone explain why this is the case? And is a better alternative to use None
instead?
Why is the empty dictionary a dangerous default value in Python? [duplicate]
Asked Answered
The problem with pasing empty list as a default argument is that it will be shared between all invocations of the function -- see the "important warning" in docs.python.org/3/tutorial/… –
Cootch
It's dangerous only if your function will modify the argument. If you modify a default argument, it will persist until the next call, so your "empty" dict will start to contain values on calls other than the first one.
Yes, using None
is both safe and conventional in such cases.
if function is not modifying the argument, should we still use None as default in the name of best practice? –
Cachinnate
@NightFurry: I hesitate to prescribe anything here--
None
is often the best choice, but perhaps not 100% of the time. Use your judgement, but if you can't decide, None
is a safe bet. –
Schrecklichkeit In case anyone else stumbles across this question, please look at this answer on optional dictionaries in function arguments. I feel it's a much better discussion on how this issue should be addressed. Basically, today one should type-hint an optional mapping of {key: value}. ie
arg: Optional[Mapping[str, str]] = None
–
Pressman If the function does not modify the argument but leaks a reference to it (by returning it, yielding it, calling some other function with it as an argument, or so on), then the same problem can occur. I would recommend always using
None
, even if the function is written carefully to avoid such bugs, purely so that seemingly-innocuous changes to the function cannot break it. –
Kristankriste That sounds like a bug in python, as far I know other dynamic programming languages will not behave that way –
Extemporize
For our case, using an empty tuple for the default argument was the two birds solution. We skip the None branch by delegating the noop case to whatever is iterating over the argument. Tuples are immutable, so we prevent the above class of bugs. Plus a bonus: simplified typehints. –
Valentijn
Let's look at an example:
def f(value, key, hash={}):
hash[value] = key
return hash
print(f('a', 1))
print(f('b', 2))
Which you probably expect to output:
{'a': 1}
{'b': 2}
But actually outputs:
{'a': 1}
{'a': 1, 'b': 2}
so how we can still achieve a default value that matches afterwards code, vs saying "if h is None: h = {}" and then continue the code ? –
Tamikotamil
I am kinda late, but I wanted to say thanks for this example. This was not something I was aware of with Python, so I was wondering why having empty list/dict was considered "dangerous". This helped a lot. –
Lyall
so
hash
becomes a global variable? –
Colp @Colp it's not global variable, you can access it only in the scope of the function, but the default value, if not supplied is referenced to the dict you defined as literal. one way to avoid it is to def func(hash=None): if hash == None: hash = {} ... –
Goodoh
I just want to say this is a real Python WTF. Who thought this could possibly be sane behaviour? –
Leven
Just bitten me as well. Tests run individually work but as a collection prior calls contaminate later ones. –
Varicelloid
This is a nice example, but AFAICT
hash
shouldn't be in the list of parameters when you only want the "expected output", above. –
Exegetics @RickyLevi you can achieve such possiblity by replacing the arg using list or dict
.copy()
or copy.deepcopy(obj)
before using the arg in the the function. So original list or dict stay safe since you're messing with a copy of it, not the original one. –
Dike Note that
hash=dict()
has the same behaviour as hash={}
. –
Gymnasium @RickyLevi I'm not sure what you mean by "afterwards code". But, I don't think you can safely achieve what you want. To be safe and write robust code, you do exactly what you suggest (as per
marxus's
comment): set the default to None
and then, in the body of the function, if the arg is None
, assign an empty dict
. That's it. Otherwise, you may have the problem that this answer describes. –
Shoon @HawkeyeParker sorry , I meant the code that comes inside the function - after the function header where i define arguments ... –
Tamikotamil
It's dangerous only if your function will modify the argument. If you modify a default argument, it will persist until the next call, so your "empty" dict will start to contain values on calls other than the first one.
Yes, using None
is both safe and conventional in such cases.
if function is not modifying the argument, should we still use None as default in the name of best practice? –
Cachinnate
@NightFurry: I hesitate to prescribe anything here--
None
is often the best choice, but perhaps not 100% of the time. Use your judgement, but if you can't decide, None
is a safe bet. –
Schrecklichkeit In case anyone else stumbles across this question, please look at this answer on optional dictionaries in function arguments. I feel it's a much better discussion on how this issue should be addressed. Basically, today one should type-hint an optional mapping of {key: value}. ie
arg: Optional[Mapping[str, str]] = None
–
Pressman If the function does not modify the argument but leaks a reference to it (by returning it, yielding it, calling some other function with it as an argument, or so on), then the same problem can occur. I would recommend always using
None
, even if the function is written carefully to avoid such bugs, purely so that seemingly-innocuous changes to the function cannot break it. –
Kristankriste That sounds like a bug in python, as far I know other dynamic programming languages will not behave that way –
Extemporize
For our case, using an empty tuple for the default argument was the two birds solution. We skip the None branch by delegating the noop case to whatever is iterating over the argument. Tuples are immutable, so we prevent the above class of bugs. Plus a bonus: simplified typehints. –
Valentijn
© 2022 - 2024 — McMap. All rights reserved.