Certainly the way default arguments work with
mutable types is not the most intuitive and I think your
complaint has some merit.
However how would you define the following to work:
def foo():
cons = [set(), [], (),]
funs = []
for ds in cons:
def g(arg:=ds):
return arg
funs.append(g)
return funs
How would you evaluate "ds" in the context of the call?
If it were to have the same observable behavior as def g(arg=ds)
except that you would get "fresh" reference on each invocation
you would get the following:
assert [f() for f in foo()] == [set(), [], ()]
Note it cannot be a simple syntactic transform because:
class _MISSING: pass
def foo():
cons = [set(), [], (),]
funs = []
for ds in cons:
def g(arg=_MISSING):
if arg is _MISSING:
arg = eval('ds') # equivalent to arg = ds so
does not produce a fresh reference
return arg
funs.append(g)
return funs
assert [f() for f in foo()] == [(), (), ()]
Granted the way closures work (especially in the context of
loops) is also a pretty unintuitive, but stands as a barrier
to easily implementing your desired behavior.
And even if that wasn't the case we still have the issue that
eval('ds') doesn't give you a fresh reference.
Would it implicitly deepcopy ds? e.g.:
class _MISSING: pass
def build_g(default):
def g(arg=_MISSING):
if arg is _MISSING:
arg =
deepcopy(default)
return arg
return g
def foo():
cons = [set(), [], (),]
funs = []
for ds in cons:
g = build_g(ds)
funs.append(g)
return funs
What if ds doesn't implement __deepcopy__?