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]) |
这是返回
在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或命名函数。请不要使用
你会惊讶于你能用一行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 变量都存储在全局命名空间中,并在函数调用完成后继续存在。因此,这不是一个好的解决方案,如果可能的话,应该避免使用。global lambda函数内部声明自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) |
您现在可以使用此
1 2 3 4 | map(lambda x: LET(('y', x + 1, 'z', x - 1), lambda o: o.y * o.z), [1, 2, 3]) |
给出:
让我尝试解决@balpha解析问题。我会在多行lamda周围使用括号。如果没有括号,lambda定义就是贪婪的。所以lambda在
1 2 3 4 5 | map(lambda x: y = x+1 z = x-1 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更痛苦。
对于丑陋的黑客,您可以使用
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