George Sakkis wrote:
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.
Why? Yes, there _might_ be performance issues (which have yet to be demonstrated or deeply speculated upon), but re-evaluating immutable default arguments wouldn't affect a program's correct operation.
I'm sure that the python-devs and the BDFL would have thought about it quite a bit when the current semantics were decided,
...which was probably a while ago. They might reconsider the issue now that some time has passed and they've seen how their decision has worked out. But yes, your analysis is a definite possibility.
and it's unlikely they'll change their mind now without very good reasons.
I hope to provide those reasons in my PEP.
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.
My PEP does discuss the possibility of adding new syntax for the new semantics and leaving the old semantics as the default. The final proposal will take into account the community's opinion as to the specifics of how the syntax/semantics ought to be changed.
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])
While that is some pretty nifty/fancy coding, the use of strings for the default values does seem a bit kludgey. However, if my proposal does not end up getting approved, I'll be sure to recommend that some of the great decorators mentioned on this thread get added to the standard library.
- Chris Rebert