Python中没有多行Lambda:为什么不呢?

问题:Python中没有多行Lambda:为什么不呢?

我听说它说不能在Python中添加多行lambda,因为它们会在语法上与Python中的其他语法结构发生冲突。我今天在公共汽车上考虑这个问题,意识到我想不出一个与多行lambda冲突的Python结构。考虑到我非常了解该语言,这令我感到惊讶。

现在,我确定Guido有一个理由不在语言中包含多行lambda,而是出于好奇:在什么情况下包含多行lambda会导致歧义?我所听到的是正确的,还是Python不允许多行lambda的其他原因?

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?


回答 0

请看以下内容:

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

这是lambda返回(y, [1,2,3])(因此映射仅获取一个参数,从而导致错误)吗?还是返回y?还是语法错误,因为新行上的逗号放置不正确?Python如何知道您想要什么?

在括号内,缩进对于python并不重要,因此您不能明确地使用多行。

这只是一个简单的例子,可能还有更多示例。

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.


回答 1

Guido van Rossum(Python的发明者)自己在一个旧的博客文章中回答了这个确切的问题。
基本上,他承认这在理论上是可行的,但是任何建议的解决方案都是非Python的:

“但是,对于我来说,任何提议的解决方案的复杂性对我来说都是巨大的:它要求解析器(或更确切地说是词法分析器)能够在缩进敏感模式和缩进不敏感模式之间来回切换,并保持堆栈技术上可以解决所有问题(已经有一堆可以概括的缩进级别了。)但是,这些都不让我直觉,这简直就是鲁伯·戈德堡的精妙之处。”

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.”


回答 2

通常这很丑陋(但有时替代方法甚至更丑陋),因此一种解决方法是制作一个大括号表达式:

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

不过,它不会接受任何分配,因此您必须事先准备数据。我发现这个有用的地方是PySide包装器,有时在其中有简短的回调。编写其他成员函数将更加难看。通常,您将不需要此。

例:

pushButtonShowDialog.clicked.connect(
    lambda: (
    field1.clear(),
    spinBox1.setValue(0),
    diag.show())

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())

回答 3

一些相关链接:

一段时间以来,我一直在关注Reia的开发,该开发最初也将基于Python的基于缩进的语法与Ruby块一起使用,全部都在Erlang之上。但是,设计师最终放弃了缩进敏感性,他写的有关该决定的文章包括有关他在缩进+多行块中遇到的问题的讨论,以及他对Guido的设计问题/决定的越来越多的赞赏:

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

另外,这是一个关于Python中Ruby样式块的有趣建议,我在Guido上发布了一个响应,但没有将其实际击倒(尽管不确定是否有任何后续击落):

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

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


回答 4

[编辑]阅读此答案。它解释了为什么多行lambda不是问题。

简而言之,这是不可思议的。来自Guido van Rossum的博客文章:

我发现任何在表达式中间嵌入基于缩进的块的解决方案都是不可接受的。由于我发现语句分组的替代语法(例如花括号或begin / end关键字)同样不可接受,因此,这几乎使多行lambda成为无法解决的难题。

[Edit] Read this answer. It explains why multiline 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.


回答 5

让我向您介绍一个光荣却可怕的技巧:

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)

您现在可以按以下LET方式使用此表单:

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

这使: [0, 3, 8]

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]


回答 6

我在一些项目中实践这种肮脏的技巧感到内which,这有点简单:

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

我希望您能找到一种保持pythonic的方法,但是如果您必须这样做的话,那么会比使用exec和操作globals减轻痛苦。

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.


回答 7

让我尝试解决@balpha解析问题。我会在多行lamda周围使用括号。如果没有括号,则lambda定义为贪婪的。所以lambda在

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

返回一个函数,该函数返回 (y*z, [1,2,3])

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

手段

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

其中func是返回y * z的多行lambda。那样有用吗?

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?


回答 8

(对于仍对该主题感兴趣的任何人。)

考虑一下这一点(即使在“多行” lambda中的其他语句中甚至使用了语句的返回值,尽管这很呕吐;-)

>>> 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

(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

回答 9

您可以简单地使用斜杠(\如果您的lambda函数有多行,则)

例:

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

Output: 30

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

回答 10

我从python开始,但是来自Javascript最明显的方法是将表达式提取为函数…。

人为的例子,乘法表达式(x*2)被提取为函数,因此我可以使用多行:

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

也许它不能完全回答这个问题,即那是如何在lambda表达式本身中执行多行操作,但是如果有人得到此线程来查找如何调试该表达式(像我一样),我认为这会有所帮助

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


回答 11

关于丑陋的黑客,您始终可以使用exec和常规函数的组合来定义多行函数,如下所示:

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

您可以将其包装为以下函数:

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')

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')

回答 12

我只是在玩些游戏,以尝试对reduce进行字典理解,并提出这个内胆技巧:

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}

我只是想做与此Javascript dict理解相同的事情:https : //stackoverflow.com/a/11068265

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


回答 13

这是多行lambda的更有趣的实现。由于python如何使用缩进来构造代码,因此无法实现。

但是幸运的是,可以使用数组和括号禁用缩进格式。

正如已经指出的那样,您可以这样编写代码:

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

从理论上讲,如果可以保证从左到右进行求值,那么它会起作用,但是仍然会丢失从一个表达式传递到另一个表达式的值。

实现较为冗长的方法的一种方法是

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

每个lambda接收前一个参数的位置。

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

这种方法使您可以编写有点像Lisp / scheme的东西。

所以你可以这样写:

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

可以使用更复杂的方法来计算斜边

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

这将返回一个标量数字列表,因此可用于将多个值减少为一个。

拥有那么多lambda肯定不会非常有效,但是如果您受到限制,那么它可能是快速完成某项工作并将其重写为实际函数的好方法。

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.


回答 14

因为lambda函数应该是单行的,因此它是函数的最简单形式, an entrance, then return

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