[Python-ideas] Syntax for late-binding of arguments [was: Default arguments ...]

Steven D'Aprano steve at pearwood.info
Fri May 15 10:44:54 CEST 2009


On Fri, 15 May 2009 03:08:18 pm Stephen J. Turnbull wrote:

> Could you summarize that discussion briefly?

Many newbies, and some more experienced programmers, are confused by the 
behaviour of functions when parameters are given default mutable 
arguments:

>>> def f(x=[]):
...     x.append(1)
...     return x
...
>>> f()
[1]
>>> f()
[1, 1]

Some people are surprised by this behaviour, and would prefer that the 
default value for x be freshly created each time it is needed. This is 
one of the most common, and most acrimonious, topics of discussion on 
comp.lang.python. The standard idiom for the expected behaviour is to 
insert boilerplate code that checks for a sentinel:

def f(x=None):
    if x is None: x = []
    x.append(1)
    return x

The chances of having the standard behaviour changed are slim, at best, 
for various reasons including backward compatibility and runtime 
efficiency. Also, I believe Guido has ruled that the standard behaviour 
will not be changed.

However, some have suggested that if the standard compile-time creation 
of defaults won't be changed, perhaps it could be made optional, with 
special syntax, or perhaps a decorator, controlling the behaviour. See 
these two proof-of-concept decorators, by Geremy Condra, for example:

http://code.activestate.com/recipes/576751/
http://code.activestate.com/recipes/576754/

I'm not convinced by decorator-based solutions, so I'll pass over them. 
I assume that any first-class solution will require cooperation from 
the compiler, and thus move the boilerplate out of the function body 
into the byte code. (Or whatever implementation is used -- others have 
suggested using thunks.)

Assuming such compiler support is possible, it only remains to decide on 
syntax for it. Most suggested syntax I've seen has marked the default 
value itself, e.g.:  def f(x = new []). Some have suggested overloading 
lambda, perhaps with some variation like def f(x = *lambda:[]).

I suggest that the markup should go on the formal parameter name, not 
the default value: we're marking the formal parameter as "special" for 
using delayed semantics, not that the default object (usually [] or {}) 
will be special.

Some years ago, Python overloaded the binary operators * and ** for use 
as special markers in parameter lists. I suggest we could do the same, 
by overloading the & operator in a similar fashion: inside the 
parameter list, &x would mean to delay evaluation of the default 
argument:

def f(x=[], &y=[])

x would use the current compile-time semantics, y would get the new 
runtime semantics.

I don't have any particular reason for choosing & over any other binary 
operator. I think ^ would also be a good choice.

Tagging a parameter with unary-& but failing to specify a default value 
should be a syntax error. Likewise for unary-& outside of a parameter 
list. (At least until such time as somebody suggests a good use for 
such a thing.)


-- 
Steven D'Aprano



More information about the Python-ideas mailing list