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.