[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