[Python-ideas] Allow lambda decorators

Guido van Rossum guido at python.org
Mon Feb 9 04:29:35 CET 2009


On Sun, Feb 8, 2009 at 4:41 PM, Carl Johnson <carl at carlsensei.com> wrote:
> 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]

I'm sorry, but you are using two nested lambdas plus a list
comprehension, and three nested functions here, plus one more list
comprehension for showing the result. My brain hurts trying to
understand all this. I don't think this bodes well as a use case for a
proposed feature.

I'm not trying to be sarcastic here -- I really think this code is too
hard to follow for a motivating example.

> 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
>
> _______________________________________________
> Python-ideas mailing list
> Python-ideas at python.org
> http://mail.python.org/mailman/listinfo/python-ideas
>



-- 
--Guido van Rossum (home page: http://www.python.org/~guido/)



More information about the Python-ideas mailing list