[Python-ideas] Allow lambda decorators

Carl Johnson carl at carlsensei.com
Mon Feb 9 01:41:37 CET 2009


A few months back there was a discussion of how code like this gives  
"surprising" results because of the scoping rules:

 >>> def func_maker():
...     fs = []
...     for i in range(10):
...         def f():
...             return i
...         fs.append(f)
...     return fs
...
 >>> [f() for f in func_maker()]
[9, 9, 9, 9, 9, 9, 9, 9, 9, 9]

Various syntax changes were proposed to get around this, but nothing  
ever came of it.

Also recently, I tried to propose a new syntax to allow Ruby-like  
blocks in Python without sacrificing Python's indenting rules. My idea  
was that "@" would mean "placeholder for a function to be defined on  
the next line" like so:

 >>> sorted(range(10), key=@):
... def @(item):
...     return -item
...
[9, 8, 7, 6, 5, 4, 3, 2, 1, 0]

Thinking about it some more, I've realized that the change I proposed  
is unnecessary, since we already have the decorator syntax. So, for  
example, the original function can be made to act with the "expected"  
scoping by using an each_in function defined as follows:

 >>> def each_in(seq):
...     return lambda f: [f(item) for item in seq]
...
 >>> def func_maker():
...     @each_in(range(10))
...     def fs(i):
...         def f():
...             return i
...         return f
...     return fs #Warning, fs is a list, not a function!
...
 >>> [f() for f in func_maker()]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

On the one hand, I can imagine some people thinking this is decorator  
abuse, since the each_in decorator produces a list and not a function.  
If so, I suppose the lambda might be changed to

 >>> def each_in(seq):
...     return lambda f: lambda: [f(item) for item in seq]
...
 >>> def func_maker():
...     @each_in(range(10))
...     def fs(i):
...         def f():
...             return i
...         return f
...     return fs() #Warning, fs is a function, not a list
...
 >>> [f() for f in func_maker()]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]


In either version the important thing is that it provides a scoped  
version of a for-loop, which those from a C++ background might be more  
conditioned to expect. Possibly, such a function could be added to the  
functools or the itertools. It would be useful for when scoping issues  
arise, for example when adding a bunch of properties or attributes to  
a class.

Thinking about it some more though, it's hard to see why such a  
trivial function is needed for the library. There's no reason it  
couldn't just be done as an inline lambda instead:

 >>> def func_maker():
...     @lambda f: [f(i) for i in range(10)]
...     def fs(i):
...         def f():
...             return i
...         return f
...     return fs #Warning, fs is a list, not a function!
...
 >>> [f() for f in func_maker()]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

OK, actually there is one reason this couldn't be done as a lambda:

 >>> @lambda f: [f(i) for i in range(10)]
   File "<stdin>", line 1
     @lambda f: [f(i) for i in range(10)]
           ^
SyntaxError: invalid syntax

This is because the decorator grammar asks for a name, not an  
expression, as one (well, OK, me a couple months ago) might naively  
expect.

decorator: '@' dotted_name [ '(' [arglist] ')' ] NEWLINE
decorators: decorator+
decorated: decorators (classdef | funcdef)

and not

decorator: '@' test NEWLINE
decorators: decorator+
decorated: decorators (classdef | funcdef)

Changing the grammar would also allow for the rewriting of the sorted  
example given earlier:

 >>> @lambda key: sorted(range(10), key=key)
... def sorted_list(item):
...     return -item
...
 >>> sorted_list
[9, 8, 7, 6, 5, 4, 3, 2, 1, 0]


Of course, the use of lambda decorator is strictly speaking unnecessary,

 >>> k = lambda key: sorted(range(10), key=key)
 >>> @k
... def sorted_list(item):
...     return -item
...
 >>> del k
 >>> sorted_list
[9, 8, 7, 6, 5, 4, 3, 2, 1, 0]

But, I think I think that allowing lambda decorators would be  
convenient for a number of situations and it would give a simple  
answer to those asking for a multiline lambda or Ruby-like blocks.

What do other people think?

-- Carl




More information about the Python-ideas mailing list