It would really make sense to change the semantics of Python to fix this issue.
It would really make sense to change the semantics of Python to fix this issue.
There are other dynamic languages with functions as first class objects which don't share the "mutable default arguments" gotcha.
But having said that, any change regarding this would break backward compatibility.
def foo(default_arg = []):
Why can't that just be shorthand for: def foo(default_arg = ParamNone):
if default_arg == ParamNone:
default_arg = []
How would that break first class functions?What breaks is something like:
def foo(default_arg = slow_f()):
pass
Under the shorthand gets turned into: ParamNone = object()
def foo(default_arg = ParamNone):
if default_arg is ParamNone:
default_arg = slow_f()
pass
This is fine, since everyone would know that the shorthand means to not put slow code there. Instead, people will start writing it as: _foo_arg = slow_f()
def foo(default_arg = _foo_arg):
pass
Of course, then what happens with: _foo_arg = slow_f()
def foo(default_arg = _foo_arg):
_foo_arg = 5
? Under expansion it becomes: _foo_arg = slow_f()
def foo(default_arg = ParamNone):
if default_arg is ParamNone:
default_arg = _foo_arg
_foo_arg.add(5)
This violates Python's scoping rules, because _foo_arg is now being used in local scope instead of global scope. Eg: >>> def f(x=None):
... if x is None:
... x = spam
... spam = 3
...
>>> spam = 9
>>>
>>> f()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 3, in f
UnboundLocalError: local variable 'spam' referenced before assignment
Which means you now need a new scoping rule, just to handle default parameters without making things more confusing.It also turns what was a simple O(1) offset into a precomputed list into a globals() lookup for many cases.
The default arguments thing is worse than a lot of the stuff Python 3 corrected.
More specifically, given a better 'default arguments thing', how would you interpret:
x = [2]
def f(x=x*5):
x.append(4)
With the earlier conversion it's: x = [2]
def f(x=DefaultArg):
if x is DefaultArg:
x = x*5
x.append(4)
This isn't going to work because the x inside of f() is different than the outside x, and you'll get the error message I mentioned.If you add a nonlocal, as in:
x = [2]
def f(x=DefaultArg):
nonlocal x
if x is DefaultArg:
x = x*5
x.append(4)
then you'll get "SyntaxError: name 'x' is parameter and nonlocal".What other solution are you thinking of?
def foo(default_arg = [0]*(256*256)):
...
and still get the same namespace issues.Memoization is not always going to be an available solution. For example, it may be that slow_f() returns a stateless object, so can be reused, while slow_f(x) returns something stateful. You can think of my examples as either using default arguments as a single element memo, or using a module variable for the same. Both premised on the idea that the developer knows enough to make the right decision.