关于语法:Python中没有Multiline Lambda:为什么不呢?

No Multiline Lambda in Python: Why not?

我听说过,不能在Python中添加多行lambda,因为它们在语法上会与Python中的其他语法结构冲突。我今天在公共汽车上考虑这个问题,我意识到我不能想到多行lambda与之冲突的单个python构造。鉴于我对语言非常了解,这让我很惊讶。

现在,我确信guido有理由不在语言中包含多行lambda,但出于好奇:在什么情况下,包含多行lambda会模糊不清?我听说的是真的吗,还是有其他原因导致python不允许多行lambda?


guido van rossum(python的发明者)在一篇旧的博客文章中回答了这个问题。基本上,他承认理论上是可行的,但任何提议的解决方案都是非Python式的:

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


请看以下内容:

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

这是返回(y, [1,2,3])的lambda吗(因此map只获取一个参数,从而导致错误)?还是返回y?或者它是语法错误,因为新行上的逗号放错了位置?Python怎么知道你想要什么?

在parens中,缩进对python来说并不重要,因此不能明确地使用多行。

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


这通常非常难看(但有时替代方案更难看),因此解决方法是制作大括号表达式:

1
2
3
4
lambda: (
    doFoo('abc'),
    doBar(123),
    doBaz())

它不会接受任何任务,所以你必须事先准备好数据。我发现这个有用的地方是Pyside包装器,在那里您有时会有短的回调。编写额外的成员函数会更难看。通常你不需要这个。

例子:

1
2
3
4
5
pushButtonShowDialog.clicked.connect(
    lambda: (
    field1.clear(),
    spinBox1.setValue(0),
    diag.show())


几个相关链接:

有一段时间,我一直在关注reia的开发,它最初也将使用基于python的带有ruby块的缩进语法,所有这些都在erlang之上。但是,设计师最终放弃了压痕敏感度,他写的这篇关于该决定的文章包括关于压痕+多行块遇到的问题的讨论,以及他对Guido的设计问题/决定所获得的更多赞赏:

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

另外,这里还有一个关于python中Ruby风格块的有趣建议,我在这里看到了guido发布的一个响应w/o实际将其击落(但不确定是否有后续的击落):

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


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

简单地说,这是不完美的。来自Guido van Rossum的博客:

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.

至于剩下的答案。使用单行1 lambda或命名函数。请不要使用exec——我很遗憾曾经建议过。

你会惊讶于你能用一行python做什么。

获取多行lambda函数的解决方案(skritos答案的扩展):

1
(lambda n: (exec('global x; x=4; x=x+n'), x-2)[-1])(0)

它做什么:

  • python简化(执行)之前元组的每个组件正在读取分隔符。

  • 例如,lambda x: (functionA(), functionB(), functionC(), 0)[-1]。将执行所有三个函数,即使只有使用的是列表中的最后一项(0)。

  • 通常情况下,不能在python中的列表或元组中分配或声明变量,但是可以使用exec函数(请注意,它总是返回:None)。

  • 请注意,除非将变量声明为global,否则它将不存在于exec函数调用之外(这仅适用于lambda语句内的exec函数)。

  • 例如,没有global声明,(lambda: exec('x=5;print(x)'))()工作正常。但是,(lambda: (exec('x=5'), exec('print(x)')))()(lambda: (exec('x=5'), x)()没有。

  • 请注意,所有global变量都存储在全局命名空间中,并在函数调用完成后继续存在。因此,这不是一个好的解决方案,如果可能的话,应该避免使用。globallambda函数内部声明自exec函数的变量与global命名空间保持分离。(在python 3.3.3中测试)

  • 元组末尾的[-1]得到最后一个索引。例如,[1,2,3,4][-1]4。这样只返回所需的输出值,而不返回包含来自exec函数和其他无关值的None的整个元组。

等效多线函数:

1
2
3
4
5
6
def function(n):
    x = 4
    x = x+n
    return x-2

function(0)

避免需要多行lambda的方法:

递归:

1
f = lambda i: 1 if i==0 or i==1 else f(i-1)+f(i-2)

布尔值是整数:

1
lambda a, b: [(0, 9), (2, 3)][a<4][b>3]

Iterators:

1
lambda x: [n**2 for n in x] #Assuming x is a list or tuple in this case


让我向你们介绍一个光荣但可怕的黑客:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
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表单,如下所示:

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

给出:[0, 3, 8]


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

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

返回返回(y*z, [1,2,3])的函数

但是

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

方法

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

func是返回y*z的多行lambda,它在哪里工作?


(对于仍感兴趣的人。)

考虑到这一点(甚至包括在"多行"lambda中的其他语句中使用语句的返回值,尽管它在呕吐点很难看;-)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
>>> 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


我对在我的一些项目中实施这种肮脏的黑客行为感到内疚,这有点简单:

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

我希望你能找到一种方法来保持Python,但如果你必须这样做,比使用exec和操纵globals更痛苦。


对于丑陋的黑客,您可以使用exec和正则函数的组合来定义这样的多行函数:

1
2
3
4
5
f = exec('''
def mlambda(x, y):
    d = y - x
    return d * d
'''
, globals()) or mlambda

您可以将其包装成如下函数:

1
2
3
4
5
6
7
8
9
10
def mlambda(signature, *lines):
    exec_vars = {}
    exec('def mlambda' + signature + ':
'
+ '
'
.join('\t' + line for line in lines), exec_vars)
    return exec_vars['mlambda']

f = mlambda('(x, y)',
            'd = y - x',
            'return d * d')

我只是想用reduce做一个听写理解,然后想出一个简单的方法:

1
2
3
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