485

I've heard it said that multiline lambdas can't be added in Python because they would clash syntactically with the other syntax constructs in Python. I was thinking about this on the bus today and realized I couldn't think of a single Python construct that multiline lambdas clash with. Given that I know the language pretty well, this surprised me.

Now, I'm sure Guido had a reason for not including multiline lambdas in the language, but out of curiosity: what's a situation where including a multiline lambda would be ambiguous? Is what I've heard true, or is there some other reason that Python doesn't allow multiline lambdas?

Imagist
  • 18,086
  • 12
  • 58
  • 77
  • 46
    **tl;dr version:** because Python is a lazy language without { } blocks and so this was not allowed in order to keep a consistent syntactical design. – Andrew Aug 11 '17 at 15:02
  • 30
    **Also: I'm thoroughly surprised no one mentioned this in the answers... You can end lines with the \ character in Python and continue onto the next line... This information kinda supersedes this whole question so...** – Andrew Aug 11 '17 at 16:48
  • 1
    https://softwareengineering.stackexchange.com/questions/99243/why-doesnt-python-allow-multi-line-lambdas – pcworld Oct 07 '18 at 17:00
  • 3
    "syntactical design" – nicolas Nov 18 '19 at 16:13
  • This would require allowing statements inside expressions. If you're going to do that, you don't need `lambda` expressions in the first place; you could simply use `def` statements in expressions. – chepner Dec 17 '19 at 18:20
  • 2
    @chepner Except that the `def` can not line inline with the logic it is intended for use: you have to go place it somewhere else and then the reader has to go hunt for it. Having a `def` for code that is only used once is a serious deficiency with the python language: those should only be needed for code re-use. – WestCoastProjects Mar 28 '20 at 17:30
  • @StephenBoesch that's not true, you can define a function right on the line before you use it. You just won't be able to use it outside of the current scope. – Mark Ransom Jan 21 '21 at 23:56
  • @MarkRansom There often is logic that I don't want to break up just before a functionals / collections processing flow. Putting the def in that place _does_ break up that logic. Any way you look at it being required to export the logic into a `def` potentially imposes awkwardness. I was a functional programmer for a half decade before spending more time with python starting in 2015: i had to completely change my style of programming - and not for the better. – WestCoastProjects Jan 22 '21 at 00:09
  • @StephenBoesch I would find a multi-line lambda in the middle of an expression to be just as awkward, if not more. I fully support your right to disagree. – Mark Ransom Jan 22 '21 at 00:42
  • @MarkRansom Your sentiments are fair given this is python. There is a different flow for programming in languages that have support for complex inline functions applied to maps/filters/reduces. It does take some getting used to - but as you can tell I _really_ dislike looking back . Python also has a mantra to prefer having only a single way to do things. Doing nested `for loop`s - with non trivial logic externalized to `def`'s - is that preferred way. It would have been more generous of the language designers to allow more choice in collections processing structures. – WestCoastProjects Jan 22 '21 at 00:55

23 Answers23

830

Guido van Rossum (the inventor of Python) answers this exact question himself in an old blog post.
Basically, he admits that it's theoretically possible, but that any proposed solution would be un-Pythonic:

"But the complexity of any proposed solution for this puzzle is immense, to me: it requires the parser (or more precisely, the lexer) to be able to switch back and forth between indent-sensitive and indent-insensitive modes, keeping a stack of previous modes and indentation level. Technically that can all be solved (there's already a stack of indentation levels that could be generalized). But none of that takes away my gut feeling that it is all an elaborate Rube Goldberg contraption."

Andy Hayden
  • 359,921
  • 101
  • 625
  • 535
Eli Courtwright
  • 186,300
  • 67
  • 213
  • 256
  • 136
    Why isn't this the top answer? It's not about the technical reasons, it's a design choice, as clearly stated by the inventor. – Dan Abramov Nov 15 '11 at 21:33
  • 22
    @DanAbramov because the OP didn't log in for years probably. – Prof. Falken Jan 10 '13 at 08:26
  • 8
    For those who didn't understood the Rube Goldberg reference, see: http://en.wikipedia.org/wiki/Rube_Goldberg_Machine – fjsj Feb 09 '13 at 22:06
  • 2
    In the UK, the equivalent is 'Heath Robinson'. – David Feb 26 '13 at 08:54
  • 75
    Guido's answer is just another reason I wish Python didn't depend on indentation to define blocks. – Mr. Lance E Sloan Apr 16 '14 at 14:12
  • 2
    Ruby does just fine with different ways of defining scope. – Max Heiber Sep 12 '14 at 20:04
  • 33
    I'm not sure I'd call "gut feeling" a design choice. ;) – Elliot Cameron Dec 04 '15 at 21:04
  • 1
    @DanAbramov the part of that article where he says "and I like it that way!" really betrays his personal bias against having lambdas in Python. – Andy May 02 '16 at 17:34
  • 3
    @ElliotCameron why not? That gut feeling led to a choice to exclude multiline lambdas from the design of Python... Many design choices wind up coming down to 'gut feelings' or intuition. – Dan Passaro Jan 27 '17 at 16:29
  • 11
    @leo-the-manic Because the word "design" implies some level of intentionality and care. Many languages *do* provide multi-line lambdas and they are very much an intentional part of the design. Python's lack of them actually has drastic implications and significantly complicates the surface syntax. (For example, both decorators and context managers could be obviated by multiline lambdas.) So this "gut feeling" is not particularly satisfying in the presence of so much existing wisdom to the contrary. Instead of complicating the parser, it complicates every Python program ever written instead. – Elliot Cameron Jan 27 '17 at 17:33
  • 6
    @ElliotCameron that you disagree with the design decision doesn't make it any less of a design decision. Neither does the fact that it comes from intuition. Also your assertion that the choice has complicated Python programs is entirely subjective... others argue that it keeps them simple – Dan Passaro Jan 27 '17 at 18:23
  • 3
    @leo-the-manic Haha of course it's subjective, just like van Rossom feeling like multiline lambdas are "un-Pythonic" (i.e. "un-van-Rossom-like"). Python's popularity isn't hurting any. It'll live on without this feature. – Elliot Cameron Jan 27 '17 at 18:41
  • 3
    THE Dan Abramov? – Hejazzman Jun 16 '18 at 20:50
  • 4
    Haskell, Idris, CoffeeScript have multiline lambdas despite indentation syntax... – aoeu256 Sep 11 '19 at 22:15
  • 4
    I don't see how Guido's reason stated there necessarily has to rule out even the use of semicolons as in `(lambda x: a=3; b=a*x; b**2)`...I do understand it, if it's just an arbitrary design choice, as it seems to be – MASL Jan 14 '20 at 18:51
  • 17
    I haven't hopped on the PyTrain quite yet but i've been thinking about it. This single thing alone, that I have to define an actual function with an actual name, in order to use it as higher order, makes me want to puke, and makes me not want to write Python. "Unpythonic" ? kill me. – Joel M Feb 01 '20 at 03:58
  • It's been 10 years and 6 months since OP. Can someone give an update on whether this is unpython3.8ic? – Joel M Feb 01 '20 at 04:00
  • 9
    This Guido's preference is root of some serious ills in this language. I wish that data science used a different language (and no I'm _not_ suggesting _java_ ..) . But here we are . – WestCoastProjects Mar 09 '20 at 15:42
183

Look at the following:

map(multilambda x:
      y=x+1
      return y
   , [1,2,3])

Is this a lambda returning (y, [1,2,3]) (thus map only gets one parameter, resulting in an error)? Or does it return y? Or is it a syntax error, because the comma on the new line is misplaced? How would Python know what you want?

Within the parens, indentation doesn't matter to python, so you can't unambiguously work with multilines.

This is just a simple one, there's probably more examples.

balpha
  • 50,022
  • 18
  • 110
  • 131
  • 136
    they could force the use of parentheses if you want to return a tuple from a lambda. IMO, this should have always been enforced to prevent such ambiguities, but oh well. – mpen Jul 28 '12 at 23:28
  • 36
    This is a simple ambiguity that must be solved by adding an extra set of parens, something that exists in many places already, e.g. generator expressions surrounded by other arguments, calling a method on an integer literal (although this need not be the case since a function name can't begin with a digit), and of course single-line lambdas as well (which may be long expressions written on multiple lines). Multi-line lambdas would not be especially different from these cases that it warrants excluding them on that basis. [This](http://stackoverflow.com/a/1233520/933416) is the real answer. – nmclean Mar 07 '14 at 13:41
  • 16
    I like how there are gazillion languages that deal with it no fret, but somehow there are some deep reasons why it's supposedly very hard if not impossible – nicolas Nov 18 '19 at 16:07
  • 4
    @nicolas that's python in a nutshell – WestCoastProjects Dec 20 '19 at 07:57
  • Reason why I don't use lambda, so under-developed in Python. – NoName Jan 13 '20 at 15:51
  • 3
    For functional programming, I'd go with Scala over Python any day. – lostsoul29 Nov 25 '20 at 16:20
  • Well, we have expression assignments now, it could work, please check my answer – Severin Pappadeux Jan 21 '21 at 23:30
  • 1
    Bad language design. – huang Jul 10 '21 at 07:34
83

This is generally very ugly (but sometimes the alternatives are even more ugly), so a workaround is to make a braces expression:

lambda: (
    doFoo('abc'),
    doBar(123),
    doBaz())

It won't accept any assignments though, so you'll have to prepare data beforehand. The place I found this useful is the PySide wrapper, where you sometimes have short callbacks. Writing additional member functions would be even more ugly. Normally you won't need this.

Example:

pushButtonShowDialog.clicked.connect(
    lambda: (
    field1.clear(),
    spinBox1.setValue(0),
    diag.show())
Sebastian Bartos
  • 2,317
  • 1
  • 20
  • 20
  • 3
    My boss was just asking for something like this in our PyQt application. Awesome! – TheGerm Jan 26 '15 at 23:55
  • 1
    Thanks for this, I was also looking for a good way to use short (but still multiline) lambdas as callbacks for our PySide UI. – Michael Leonard Nov 18 '16 at 03:19
  • And now I've seen this it instantly suggested using `lambda arg` and `setattr(arg, 'attr','value')` to subvert "no assignments ...". And then there's short-circuit evaluation of `and` and `or` ... it's the Javascript that does it. Sinks roots into you, like ivy into a wall. I almost hope I forget this over Xmas. – nigel222 Dec 20 '18 at 17:47
  • quite clever - and quite readable. Now - about those (missing..) _assignments_ .. – WestCoastProjects Mar 09 '20 at 15:44
  • 1
    @nigel222 why feel ashamed? The python language is fundamentally crippled - but it's the one used _anyways_ for much of _data science_. So we make adjustments. Finding ways to do side effects (often enough simply printing /logging!) and assignments (often enough to just intermediate vars!) should be well handled by the language. But they aren't even supported (at least if you follow `PEP` guidelines) – WestCoastProjects Mar 09 '20 at 15:45
  • @javadba I think we now have assignments by abusing the "walrus" operator in Python 3.8 inside a tuple definition (but I don't want to even think about this until I can say that all the alternatives are worse). – nigel222 Mar 10 '20 at 09:28
  • @nigel222 Yes - i ran into that 2 days ago - it's a nice find! I have updated my laptop and lab servers to 3.8 . `pyspark` seems to be a bit away from 3.8 just yet - but getting close. – WestCoastProjects Mar 10 '20 at 13:21
  • @nigel222 Yes, the assignment operator lets you approximate multi-line lambdas now! Please upvote [this answer](https://stackoverflow.com/a/65837289) mentioning it. Also, here's a [demo](https://replit.com/@Venryx/PythonMultiLineLambdas#main.py) of it in use. – Venryx Apr 03 '22 at 22:59
22

A couple of relevant links:

For a while, I was following the development of Reia, which was initially going to have Python's indentation based syntax with Ruby blocks too, all on top of Erlang. But, the designer wound up giving up on indentation sensitivity, and this post he wrote about that decision includes a discussion about problems he ran into with indentation + multi-line blocks, and an increased appreciation he gained for Guido's design issues/decisions:

http://www.unlimitednovelty.com/2009/03/indentation-sensitivity-post-mortem.html

Also, here's an interesting proposal for Ruby-style blocks in Python I ran across where Guido posts a response w/o actually shooting it down (not sure whether there has been any subsequent shoot down, though):

http://tav.espians.com/ruby-style-blocks-in-python.html

Anon
  • 11,870
  • 3
  • 23
  • 19
19

Let me present to you a glorious but terrifying hack:

import types

def _obj():
  return lambda: None

def LET(bindings, body, env=None):
  '''Introduce local bindings.
  ex: LET(('a', 1,
           'b', 2),
          lambda o: [o.a, o.b])
  gives: [1, 2]

  Bindings down the chain can depend on
  the ones above them through a lambda.
  ex: LET(('a', 1,
           'b', lambda o: o.a + 1),
          lambda o: o.b)
  gives: 2
  '''
  if len(bindings) == 0:
    return body(env)

  env = env or _obj()
  k, v = bindings[:2]
  if isinstance(v, types.FunctionType):
    v = v(env)

  setattr(env, k, v)
  return LET(bindings[2:], body, env)

You can now use this LET form as such:

map(lambda x: LET(('y', x + 1,
                   'z', x - 1),
                  lambda o: o.y * o.z),
    [1, 2, 3])

which gives: [0, 3, 8]

divs1210
  • 690
  • 1
  • 8
  • 16
  • 2
    Originally posted at https://gist.github.com/divs1210/d218d4b747b08751b2a232260321cdeb – divs1210 Jun 23 '17 at 20:14
  • 4
    This is awesome! I think I'll use this next time I write Python. I'm primarily a Lisp and JS programmer, and the lack of multi line lambada hurts. This is a way to get that. – Alexis Dumas Aug 02 '17 at 20:58
  • While I love lisp, and I love python, I don't love writing lisp style in python. It's unpythonic. – Iiridayn May 11 '23 at 22:10
18

I'm guilty of practicing this dirty hack in some of my projects which is bit simpler:

    lambda args...:( expr1, expr2, expr3, ...,
            exprN, returnExpr)[-1]

I hope you can find a way to stay pythonic but if you have to do it this less painful than using exec and manipulating globals.

S.Rad
  • 307
  • 2
  • 10
17

[Edit Edit] Since this question is somehow still active 12 years after being asked. I will continue the tradition of amending my answer every 4 years or so.

Firstly, the question was how does multi-line lambda clash with Python. The accepted answer shows how with a simple example. The highly rated answer I linked below some years ago answers the question of "Why is it not a part of Python"--this answer is perhaps more satisfying to those who believe that the existing examples of "clashing" are not enough to make multi-line lambda impossible to implement in Python.

In previous iterations of this answer I discussed how to implement multi-line lambda into Python as is. I've since removed that part, because it was a flurry of bad practices. You may see it in the edit history of this answer if you wish.

However the answer to "Why not?", being "because Rossum said so" can still be a source of frustration. So lets see if it could be engineered around the counter example given by user balpha:

map(lambda x:
        y=x+1 # <-- this line defines the outmost indent level*
        for i in range(12):
            y+=12
        return y
   , [1,2,3])

#*By convention it is always one-indent past the 'l' in lambda

As for the return value we have that the following is non-permissible in python:

def f():
  return 3
, [1,2,3]

So by the same logic, "[1,2,3]" should not be part of the return value. Let's try it this way instead:

map(lambda x:
        y=x+1                    # part of lambda block
        for i in range(12):      # part of lambda block
            y+=12                # part of lambda block
        return y, [1,2,3])       # part of lambda block

This one's trickier, but since the lambda block has a clearly defined beginning (the token 'lambda') yet no clear ending, I would argue anything that is on the same line as part of a lambda block is also part of the lambda block.

One might imagine some features that can identify closing parenthesis or even inference based on the number of tokens expected by the enclosing element. In general, the above expression does not seem totally impossible to parse, but it may be a bit of a challenge.

To simplify things, you could separate all characters not intended to be part of the block:

map(lambda x:
        y=x+1                    # part of lambda block
        for i in range(12):      # part of lambda block
            y+=12                # part of lambda block
        return y                 # part of lambda block
, [1,2,3]) # argument separator, second argument, and closing paren for map

Back to where we were but this time it is unambiguous, because the last line is behind the lowest indent-depth for the lambda block. Single line lambda would be a special case (identified by the lack of an immediate newline after the color), that behaves the same as it does now.

This is not to say that it necessarily should be a part of Python--but it is a quick illustration that is perhaps is possible with some changes in the language.

[Edit] Read this answer. It explains why multi-line lambda is not a thing.

Simply put, it's unpythonic. From Guido van Rossum's blog post:

I find any solution unacceptable that embeds an indentation-based block in the middle of an expression. Since I find alternative syntax for statement grouping (e.g. braces or begin/end keywords) equally unacceptable, this pretty much makes a multi-line lambda an unsolvable puzzle.

Samie Bencherif
  • 1,285
  • 12
  • 27
10

Let me also throw in my two cents about different workarounds.

How is a simple one-line lambda different from a normal function? I can think only of lack of assignments, some loop-like constructs (for, while), try-except clauses... And that's it? We even have a ternary operator - cool! So, let's try to deal with each of these problems.

Assignments

Some guys here have rightly noted that we should take a look at lisp's let form, which allows local bindings. Actually, all the non state-changing assignments can be performed only with let. But every lisp programmer knows that let form is absolutely equivalent to call to a lambda function! This means that

(let ([x_ x] [y_ y])
  (do-sth-with-x-&-y x_ y_))

is the same as

((lambda (x_ y_)
   (do-sth-with-x-&-y x_ y_)) x y)

So lambdas are more than enough! Whenever we want to make a new assignment we just add another lambda and call it. Consider this example:

def f(x):
    y = f1(x)
    z = f2(x, y)
    return y,z

A lambda version looks like:

f = lambda x: (lambda y: (y, f2(x,y)))(f1(x))

You can even make the let function, if you don't like the data being written after actions on the data. And you can even curry it (just for the sake of more parentheses :) )

let = curry(lambda args, f: f(*args))
f_lmb = lambda x: let((f1(x),), lambda y: (y, f2(x,y)))
# or:
f_lmb = lambda x: let((f1(x),))(lambda y: (y, f2(x,y)))

# even better alternative:
let = lambda *args: lambda f: f(*args)
f_lmb = lambda x: let(f1(x))(lambda y: (y, f2(x,y)))

So far so good. But what if we have to make reassignments, i.e. change state? Well, I think we can live absolutely happily without changing state as long as task in question doesn't concern loops.

Loops

While there's no direct lambda alternative for loops, I believe we can write quite generic function to fit our needs. Take a look at this fibonacci function:

def fib(n):
    k = 0
    fib_k, fib_k_plus_1 = 0, 1
    while k < n:
        k += 1
        fib_k_plus_1, fib_k = fib_k_plus_1 + fib_k, fib_k_plus_1
    return fib_k

Impossible in terms of lambdas, obviously. But after writing a little yet useful function we're done with that and similar cases:

def loop(first_state, condition, state_changer):
    state = first_state
    while condition(*state):
        state = state_changer(*state)
    return state

fib_lmb = lambda n:\
            loop(
              (0,0,1),
              lambda k, fib_k, fib_k_plus_1:\
                k < n,
              lambda k, fib_k, fib_k_plus_1:\
                (k+1, fib_k_plus_1, fib_k_plus_1 + fib_k))[1]

And of course, one should always consider using map, reduce and other higher-order functions if possible.

Try-except and other control structs

It seems like a general approach to this kind of problems is to make use of lazy evaluation, replacing code blocks with lambdas accepting no arguments:

def f(x):
    try:    return len(x)
    except: return 0
# the same as:
def try_except_f(try_clause, except_clause):
    try: return try_clause()
    except: return except_clause()
f = lambda x: try_except_f(lambda: len(x), lambda: 0)
# f(-1) -> 0
# f([1,2,3]) -> 3

Of course, this is not a full alternative to try-except clause, but you can always make it more generic. Btw, with that approach you can even make if behave like function!

Summing up: it's only natural that everything mentioned feels kinda unnatural and not-so-pythonically-beautiful. Nonetheless - it works! And without any evals and other trics, so all the intellisense will work. I'm also not claiming that you shoud use this everywhere. Most often you'd better define an ordinary function. I only showed that nothing is impossible.

heinwol
  • 438
  • 4
  • 10
  • It's crazy! Cool! – plinyar Dec 07 '20 at 19:14
  • @aoeu256 I don't quite get it, could you provide some example/doc, please? – heinwol Mar 25 '21 at 11:20
  • This is very helpful. While I would never write real code like this, I use a number of tools that incorporate python as an extension language, and which require expressions for certain inputs. These tips let me stretch the boundaries of what I can enter as an expression. My record so far includes converting 300 lines of procedural code into a single functional expression. – pavon Mar 31 '22 at 20:58
9

After Python3.8, there is another method for local binding

lambda x: (
    y := x + 1,
    y ** 2
)[-1]

For Loop

lambda x: (
    y := x ** 2,
    [y := y + x for _ in range(10)],
    y
)[-1]

If Branch

lambda x: (
    y := x ** 2,
    x > 5 and [y := y + x for _ in range(10)],
    y
)[-1]

Or

lambda x: (
    y := x ** 2,
    [y := y + x for _ in range(10)] if x > 5 else None,
    y
)[-1]

While Loop

import itertools as it
lambda x: (
    l := dict(y = x ** 2),
    cond := lambda: l['y'] < 100,
    body := lambda: l.update(y = l['y'] + x),
    *it.takewhile(lambda _: cond() and (body(), True)[-1], it.count()),
    l['y']
)[-1]

Or

import itertools as it
from types import SimpleNamespace as ns
lambda x: (
    l := ns(y = x ** 2),
    cond := lambda: l.y < 100,
    body := lambda: vars(l).update(y = l.y + x),
    *it.takewhile(lambda _: cond() and (body(), True)[-1], it.count()),
    l.y
)[-1]

Or

import itertools as it
lambda x: (
    y := x ** 2,
    *it.takewhile(lambda t: t[0],
    ((
    pred := y < 100,
    pred and (y := y + x))
    for _ in it.count())),
    y
)[-1]
YouJiacheng
  • 449
  • 3
  • 11
6

Let me try to tackle @balpha parsing problem. I would use parentheses around the multiline lamda. If there is no parentheses, the lambda definition is greedy. So the lambda in

map(lambda x:
      y = x+1
      z = x-1
      y*z,
    [1,2,3]))

returns a function that returns (y*z, [1,2,3])

But

map((lambda x:
      y = x+1
      z = x-1
      y*z)
    ,[1,2,3]))

means

map(func, [1,2,3])

where func is the multiline lambda that return y*z. Does that work?

Wai Yip Tung
  • 18,106
  • 10
  • 43
  • 47
5

(For anyone still interested in the topic.)

Consider this (includes even usage of statements' return values in further statements within the "multiline" lambda, although it's ugly to the point of vomiting ;-)

>>> def foo(arg):
...     result = arg * 2;
...     print "foo(" + str(arg) + ") called: " + str(result);
...     return result;
...
>>> f = lambda a, b, state=[]: [
...     state.append(foo(a)),
...     state.append(foo(b)),
...     state.append(foo(state[0] + state[1])),
...     state[-1]
... ][-1];
>>> f(1, 2);
foo(1) called: 2
foo(2) called: 4
foo(6) called: 12
12
vencik
  • 86
  • 1
  • 2
  • This doesn't work when called a second time with different parameters and causes a memory leak unless the first line is `state.clear()` since default arguments are only created once when the function is created. – Matthew D. Scholefield Aug 10 '18 at 18:12
4

Here's a more interesting implementation of multi line lambdas. It's not possible to achieve because of how python use indents as a way to structure code.

But luckily for us, indent formatting can be disabled using arrays and parenthesis.

As some already pointed out, you can write your code as such:

lambda args: (expr1, expr2,... exprN)

In theory if you're guaranteed to have evaluation from left to right it would work but you still lose values being passed from one expression to an other.

One way to achieve that which is a bit more verbose is to have

lambda args: [lambda1, lambda2, ..., lambdaN]

Where each lambda receives arguments from the previous one.

def let(*funcs):
    def wrap(args):
        result = args                                                                                                                                                                                                                         
        for func in funcs:
            if not isinstance(result, tuple):
                result = (result,)
            result = func(*result)
        return result
    return wrap

This method let you write something that is a bit lisp/scheme like.

So you can write things like this:

let(lambda x, y: x+y)((1, 2))

A more complex method could be use to compute the hypotenuse

lst = [(1,2), (2,3)]
result = map(let(
  lambda x, y: (x**2, y**2),
  lambda x, y: (x + y) ** (1/2)
), lst)

This will return a list of scalar numbers so it can be used to reduce multiple values to one.

Having that many lambda is certainly not going to be very efficient but if you're constrained it can be a good way to get something done quickly then rewrite it as an actual function later.

Loïc Faure-Lacroix
  • 13,220
  • 6
  • 67
  • 99
4

In Python 3.8/3.9 there is Assignment Expression, so it could be used in lambda, greatly expanding functionality

E.g., code

#%%
x = 1
y = 2

q = list(map(lambda t: (
    tx := t*x,
    ty := t*y,
    tx+ty
)[-1], [1, 2, 3]))

print(q)

will print [3, 6, 9]

Venryx
  • 15,624
  • 10
  • 70
  • 96
Severin Pappadeux
  • 18,636
  • 3
  • 38
  • 64
  • This seems like the best solution by far! Why is it not upvoted more? – Venryx Apr 03 '22 at 22:56
  • Here is a Python code sandbox showing it in action!: https://replit.com/@Venryx/PythonMultiLineLambdas#main.py – Venryx Apr 03 '22 at 22:56
  • Possible, but it is weird, the syntax is changing, `=` becomes `:=` and have to end the line in a comma, – 27px Aug 08 '22 at 04:02
2

On the subject of ugly hacks, you can always use a combination of exec and a regular function to define a multiline function like this:

f = exec('''
def mlambda(x, y):
    d = y - x
    return d * d
''', globals()) or mlambda

You can wrap this into a function like:

def mlambda(signature, *lines):
    exec_vars = {}
    exec('def mlambda' + signature + ':\n' + '\n'.join('\t' + line for line in lines), exec_vars)
    return exec_vars['mlambda']

f = mlambda('(x, y)',
            'd = y - x',
            'return d * d')
Matthew D. Scholefield
  • 2,977
  • 3
  • 31
  • 42
2

I know it is an old question, but for the record here is a kind of a solution to the problem of multiline lambda problem in which the result of one call is consumed by another call.

I hope it is not super hacky, since it is based only on standard library functions and uses no dunder methods.

Below is a simple example in which we start with x = 3 and then in the first line we add 1 and then in the second line we add 2 and get 6 as the output.

from functools import reduce

reduce(lambda data, func: func(data), [
    lambda x: x + 1,
    lambda x: x + 2
], 3)

## Output: 6
sztal
  • 287
  • 2
  • 10
  • 1
    It's not hacky, in fact several years back I just made the above a convenience function: the args are the `apply(list, [fns])`. but it's still awkward. The root of the problem is that lambdas just suck in python. Single expression only, one line only, and even the keyword lambda gets real old real fast – WestCoastProjects Nov 02 '20 at 12:42
  • Actually, it's a popular approach. The package `toolz` have this and other fancy functional stuff. With it, your example might be rewritten as: `toolz.pipe(3, lambda x: x + 1, lambda x: x + 3)`. – heinwol Dec 07 '20 at 15:30
  • you could use assignment expressions in lambda for that, don't need to chain lambdas, check my answer below – Severin Pappadeux Jan 21 '21 at 23:34
0

I was just playing a bit to try to make a dict comprehension with reduce, and come up with this one liner hack:

In [1]: from functools import reduce
In [2]: reduce(lambda d, i: (i[0] < 7 and d.__setitem__(*i[::-1]), d)[-1], [{}, *{1:2, 3:4, 5:6, 7:8}.items()])                                                                                                                                                                 
Out[3]: {2: 1, 4: 3, 6: 5}

I was just trying to do the same as what was done in this Javascript dict comprehension: https://stackoverflow.com/a/11068265

rodfersou
  • 914
  • 6
  • 10
0

You can simply use slash (\) if you have multiple lines for your lambda function

Example:

mx = lambda x, y: x if x > y \
     else y
print(mx(30, 20))

Output: 30
IRSHAD
  • 2,855
  • 30
  • 39
  • The question concerns itself with using more than 1 expression rather than more than 1 literal line. – TZubiri Oct 30 '19 at 21:27
0

I am starting with python but coming from Javascript the most obvious way is extract the expression as a function....

Contrived example, multiply expression (x*2) is extracted as function and therefore I can use multiline:

def multiply(x):
  print('I am other line')
  return x*2

r = map(lambda x : multiply(x), [1, 2, 3, 4])
print(list(r))

https://repl.it/@datracka/python-lambda-function

Maybe it does not answer exactly the question if that was how to do multiline in the lambda expression itself, but in case somebody gets this thread looking how to debug the expression (like me) I think it will help

Vicens Fayos
  • 740
  • 1
  • 5
  • 18
  • 2
    Why would I do that and not simply write `map(multiply, [1, 2, 3])`? – thothal Mar 19 '20 at 10:13
  • 1
    Then you have to go hunt for wherever `multiply()` is. It makes one lose the flow of the code, unlike `javascript` in which the map logic is contained right there within the following block – WestCoastProjects Nov 02 '20 at 12:45
0

We can use inner classes to obtain multi-statement lambdas:

class Foo():
    def __init__(self, x):
        self.x=x

def test1(i):
    i.x += 1
    class closure():
        @staticmethod
        def call():
          i.x += 1
          print('lamba one value:', i.x)
    return closure.call

def test2(i):
    class closure():
        @staticmethod
        def call():
          i.x += 1
          print('lambda two value:', i.x)
    return closure.call

u = Foo(10)
test1(u)() # print 12
u = Foo(20)
test2(u)() # print 21
frhack
  • 4,862
  • 2
  • 28
  • 25
0

My hacky solution how to achieve multiline lambdas:

import re


def inline_func(code: str, globals_=None, locals_=None):
    if globals_ is None: globals_ = globals()
    if locals_ is None: locals_ = locals()

    lines = code.splitlines(keepends=True)
    indent = None
    func_name = None
    for i, line in enumerate(lines):
        if indent is None:
            if m := re.match(r'^(\s*)def\s+(\w+)', line):
                indent, func_name = m.groups()
                lines[i] = line.replace(indent, '', 1)
        else:
            lines[i] = line.replace(indent, '', 1)

    code = ''.join(lines).strip()
    exec(code, globals_, locals_)
    return locals_[func_name]


assert list(map(inline_func(
    '''
    def f(x):
        return (x + 1) ** 2
    '''
    ),
    range(3)
)) == [1, 4, 9]

It takes textual definition of a function, creates real callable function and returns it. And with IDEs like Pycharm which can inject syntax highlighting into strings, this solution is not even that bad as it can be edited as normal code:)

icaine
  • 329
  • 2
  • 6
0

Not multiline but multioperation lambda (working on python 3.8+)

>>> foo = lambda x: (x := x**2, x + 1)[-1] # f = x^2 + 1
>>> foo(4)
17
-1

One safe method to pass any number of variables between lambda items:

print((lambda: [
    locals().__setitem__("a", 1),
    locals().__setitem__("b", 2),
    locals().__setitem__("c", 3),
    locals().get("a") + locals().get("b") + locals().get("c")
])()[-1])

Output: 6

BaiJiFeiLong
  • 3,716
  • 1
  • 30
  • 28
  • 2
    The contents of this dictionary(locals()) should not be modified; changes may not affect the values of local and free variables used by the interpreter. https://docs.python.org/3/library/functions.html#locals – YouJiacheng May 06 '22 at 10:29
-4

because a lambda function is supposed to be one-lined, as its the simplest form of a function, an entrance, then return

Sphynx-HenryAY
  • 746
  • 6
  • 10