[Python-ideas] Multi Statement Lambdas

Chris Angelico rosuav at gmail.com
Tue Oct 23 07:15:11 EDT 2018


On Tue, Oct 23, 2018 at 8:04 PM Vladimir Filipović <hemflit at gmail.com> wrote:
>
> Chris, I'm happy to work with you to hammer out comparisons of various solutions.
>
> But I can't take on the role of an advocate for "multi-statement lambdas". I don't even understand what precisely that covers, since we don't have uni-statement lambdas.
> _If that role would be needed for this discussion, the rest of what I'm about to write may be a waste of your time!_
>

If you were confused by my use of the generic "you", then I apologize.
This wasn't aimed personally at you, but more generally at "this is
what we need".

And you're right that we don't have uni-statement lambdas, but I
suspect people would be more confused by "suite lambdas" as an
alternative to "expression lambdas", unless they happen to be familiar
with the internal details of CPython's grammar :) I suppose in theory
you could design a syntax for lambda functions that permits a
simple_stmt, or maybe a compound_stmt, but both would be as messy as
supporting a full suite (or multiple statements, in vernacular) and
would be unnecessarily restrictive.

> The central point of Python's lambdas, for me, is that when one needs to refer to a _very simple_, _once-off_ function, lambdas _make the code better._
> The ways they make it better go beyond saving vertical space and can be subtle: they improve the locality of code by allowing the function's definition to be exactly in the place where it's used; a lot like the difference between (in pseudocode) `output("Hello, world")` and having to say `String hw = new String("Hello, world"); output(hw)`.
>

My view, which I suspect is the same as yours but written in different
words, is that a lambda function should be treated as a block of code
inside another function, and not as its own entity. You don't give a
name to the block of code between "if" and "else", because it's just
code as part of the implementation of its containing function. You
don't give a name to the implicit function created by a list
comprehension, because it's just ... you get the idea.

> Okay now, what does the decorator solution do in that WebSocket situation that just defining named functions doesn't already?
>
> I suppose it helps somewhat with property #2 above (finding a place for the definition).
> Outside of that list, it lets us shorten the constructor call.
>
> Am I missing something? Or am I moving the goalposts - were you explicitly trying to solve something else?

Part of getting a proposal accepted is showing that there is no
adequate solution using existing tools. The best way to show that is
to come up with the best possible solution that we currently have, and
demonstrate (if it isn't self-evident) that it's sub-par.

The decorator solution is one of two that came to my mind as existing
options (the other being an abuse of class syntax). Criticizing it is
exactly correct, and is a way to support the assertion "we need better
lambda functions".

> Let me try to anticipate one more existing solution (the best I came up with):
>
> Where the statement is about updating counters or similar state variables, we can make them items in a dict (or collections.Counter) instead, and update them in a lambda via dict.update().
>
> It's not terrible, but it means all other references to the state variable must change in a way that makes them less clear. ("Why are they using this dict at all? Ohh, because lambda.")
>
> And having to replace `c += 1` with `d.update(c = d['c'] + 1)` or `counter.update(c=1)` is still ugly.

Also a point worth raising. Currently, lambda functions are permitted
to mutate objects in surrounding scopes, but not to rebind them. Well,
actually, PEP 572 might well be the solution there, but then you
reopen all that lovely controversy...

I mentioned abusing class syntax as an alternative solution. This
would require a new API, but it wouldn't be hard to write a wrapper.
It'd end up something like this:

@args(websocket.WebSocketApp, url)
class ws:
    def on_open(...):
    def on_message(...):

The decorator could look something like this (untested):

def args(func, *a, **kw):
    def wrapper(cls):
        for name, val in cls.__dict__.items():
            if not name.startswith("_"):
                assert name not in kw
                kw[name] = val
        return func(*a, **kw)
    return wrapper

In some contexts, that would be every bit as good as a lambda-based
solution. To compete, the proposed lambda syntax would have to be
better than all these - by no means impossible, but it's a target to
aim at.

ChrisA


More information about the Python-ideas mailing list