Chris Rebert wrote:
The following is a proto-PEP based on the discussion in the thread "fixing mutable default argument values". Comments would be greatly appreciated.
As I see it, the main objection to this is the inversal of the current default semantics: every default argument is treated as if it were mutable, or re-evaluatable more generally. Although mutable default arguments are useful some times and would be nice to have, they are most likely less common than the immutable ones, so the latter should be the default. I'm sure that the python-devs and the BDFL would have thought about it quite a bit when the current semantics were decided, and it's unlikely they'll change their mind now without very good reasons.
OTOH, a proposal that leaves the current semantics as is and adds a mechanism to specify default arguments to be evaluated at call-time rather than definition-time would have more chances. Here's a proof-of-concept solution that specifies explicitly the re-evaluatable default expressions as "deferred":
class Deferred(object): def __init__(self, expr): self.expr = expr
def eval_deferreds(func): varnames,_,_,defaults = inspect.getargspec(func) num_varnames = len(varnames); num_defaults = len(defaults) def wrapper(*args, **kwds): if len(args) >= num_varnames: # defaults not used here return func(*args,**kwds) f_locals = dict(zip(varnames,args)) used_defaults = min(num_defaults, num_varnames-len(args)) for var,default in zip(varnames[-used_defaults:], defaults[-used_defaults:]): if var in kwds: # passed as keyword argument; don't use the default value = kwds[var] elif not isinstance(default, Deferred): # non re-evaluatable default value = default else: # evaluatable default in f_locals value = eval(default.expr, func.func_globals, f_locals) f_locals[var] = value f_locals.update(kwds) # add any extra keyword arguments return func(**f_locals) return wrapper
#======= example ==============================
W = 1 # some global
@eval_deferreds def f(x, y=Deferred('x**2+W'), z=Deferred('')): z.append(x) z.append(y) return z
from collections import deque print f(3) # [3,10] W=3; print f(4) # [4,19] print f(4,5) # [4,5] print f(-1, z=deque()) # deque([-1,4])