[Python-ideas] proto-PEP: Fixing Non-constant Default Arguments

Chris Rebert cvrebert at gmail.com
Tue Jan 30 06:40:35 CET 2007

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":
> import inspect
> 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

More information about the Python-ideas mailing list