我听说在Python中不能添加多行lambdas,因为它们会在语法上与Python中的其他语法结构冲突。今天在公交车上,我一直在思考这个问题,并意识到我想不出任何一个Python构造可以与多行lambdas相冲突。考虑到我对这门语言相当熟悉,这让我很惊讶。
现在,我相信Guido没有在语言中包含多行lambda是有原因的,但出于好奇:在什么情况下,包含多行lambda会有歧义?我听说的是真的吗,还是有其他原因导致Python不允许多行lambda ?
我听说在Python中不能添加多行lambdas,因为它们会在语法上与Python中的其他语法结构冲突。今天在公交车上,我一直在思考这个问题,并意识到我想不出任何一个Python构造可以与多行lambdas相冲突。考虑到我对这门语言相当熟悉,这让我很惊讶。
现在,我相信Guido没有在语言中包含多行lambda是有原因的,但出于好奇:在什么情况下,包含多行lambda会有歧义?我听说的是真的吗,还是有其他原因导致Python不允许多行lambda ?
当前回答
Guido van Rossum (Python的发明者)在一篇旧博客文章中亲自回答了这个问题。 基本上,他承认这在理论上是可能的,但任何提出的解决方案都是非python的:
“但对我来说,这个谜题的任何解决方案的复杂性都是巨大的:它要求解析器(或者更准确地说,词法分析器)能够在缩进敏感和不缩进模式之间来回切换,保持先前模式的堆栈和缩进水平。从技术上讲,这些问题都可以解决(已经有一堆缩进级别可以被一般化)。但这些都不能改变我的直觉,那就是这一切都是一个精心设计的鲁布·戈德堡(Rube Goldberg)的精巧装置。”
其他回答
[编辑编辑]因为这个问题在被问到12年后,不知何故仍然活跃。我将延续每四年左右修改一次答案的传统。
首先,问题是多行lambda如何与Python冲突。公认的答案用一个简单的例子说明了如何做到这一点。几年前,我在下面链接了一个评分很高的答案,回答了“为什么它不是Python的一部分”这个问题——对于那些认为现有的“冲突”示例不足以使多行lambda无法在Python中实现的人来说,这个答案可能更令人满意。
在这个答案的前面迭代中,我讨论了如何在Python中实现多行lambda。后来我删除了这部分,因为这是一堆糟糕的做法。如果你愿意,你可以在这个答案的编辑历史中看到它。
然而,“为什么不呢?”的答案是“因为Rossum这么说”,这仍然可能是沮丧的来源。所以让我们看看它是否可以围绕用户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
至于我们的返回值,在python中是不允许的:
def f():
return 3
, [1,2,3]
所以按照同样的逻辑,"[1,2,3]"不应该是返回值的一部分。让我们换个方式试试:
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
这一点比较棘手,但由于lambda块有一个明确定义的开始(令牌'lambda'),但没有明确的结束,我认为作为lambda块的一部分在同一行上的任何东西也是lambda块的一部分。
人们可能会想象一些可以识别闭括号的特性,甚至可以基于封闭元素所期望的标记数量进行推断。一般来说,上面的表达式似乎不是完全不可能解析,但它可能有点挑战。
为了简化,你可以分离所有不打算成为块的一部分的字符:
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
回到我们刚才的地方,但这一次它是明确的,因为最后一行位于lambda块的最低缩进深度后面。 单行lambda是一种特殊情况(通过在颜色后面没有立即换行来标识),其行为与现在相同。
这并不是说它一定要成为Python的一部分——但这只是一个简单的说明,在语言中做一些更改也许是可能的。
[编辑]阅读下面的答案。它解释了为什么多行不存在。
简单地说,它是非python的。Guido van Rossum在博客中写道:
我发现任何在表达式中间嵌入基于缩进的块的解决方案都是不可接受的。由于我发现语句分组的替代语法(例如大括号或开始/结束关键字)同样不可接受,这几乎使多行lambda成为一个无法解决的难题。
关于丑陋的黑客,你总是可以使用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')
在Python3.8之后,还有另一个用于本地绑定的方法
lambda x: (
y := x + 1,
y ** 2
)[-1]
For循环
lambda x: (
y := x ** 2,
[y := y + x for _ in range(10)],
y
)[-1]
如果分支
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循环
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]
在Python 3.8/3.9中有赋值表达式,因此可以在lambda中大量使用 扩展功能
例如,代码
#%%
x = 1
y = 2
q = list(map(lambda t: (
tx := t*x,
ty := t*y,
tx+ty
)[-1], [1, 2, 3]))
print(q)
将打印[3,6,9]
如果lambda函数有多行,可以简单地使用斜杠(\)
例子:
mx = lambda x, y: x if x > y \
else y
print(mx(30, 20))
Output: 30