听说在Python中添加多行lambda表达式是不可能的,因为它们在语法上会与Python中的其他语法结构发生冲突。今天在公交车上思考这个问题时,我意识到我无法想到任何一个Python结构与多行lambda表达式发生冲突。考虑到我对该语言相当熟悉,这让我感到惊讶。
现在,我确定Guido没有将多行lambda表达式包含在语言中的原因,但出于好奇:包含多行lambda表达式会导致模糊不清的情况吗?我所听到的是真的吗?还是Python不允许多行lambda表达式的原因是其他方面的原因?
看以下内容:
map(multilambda x:
y=x+1
return y
, [1,2,3])
这个 lambda 函数返回的是 (y, [1,2,3])
吗?这样 map 函数只会得到一个参数,导致出错?还是它返回 y
?或者是语法错误,因为换行后的逗号位置不正确?Python 怎么知道你想要什么?
在括号中,缩进对 Python 来说并不重要,所以不能明确地处理多行代码。
这只是一个简单的例子,可能还有其他情况。
这通常非常丑陋(但有时替代方案更加丑陋),因此一种解决方法是创建一个括号表达式:
lambda: (
doFoo('abc'),
doBar(123),
doBaz())
它不会接受任何任务,因此您需要提前准备数据。
我发现这在PySide封装器中非常有用,有时候您可能需要使用短的回调函数。编写额外的成员函数将更加丑陋。通常情况下,您不需要这样做。
示例:
pushButtonShowDialog.clicked.connect(
lambda: (
field1.clear(),
spinBox1.setValue(0),
diag.show())
lambda arg
和setattr(arg,'attr','value')
来规避“无赋值...”的问题。然后有and
和or
的短路求值...这是Javascript做的。就像常春藤一样扎根于你的内心,几乎希望自己能在圣诞节忘记这件事情。 - nigel222一些相关链接:
我曾经一段时间关注Reia的发展情况,它最初打算在Erlang的基础上采用Python的缩进语法和Ruby块,但是设计者最终放弃了缩进灵敏度。他写的这篇文章包括了他在缩进和多行块方面遇到的问题,并增加了他对Guido的设计问题/决策的欣赏:
http://www.unlimitednovelty.com/2009/03/indentation-sensitivity-post-mortem.html
此外,我看到了一个有趣的提议,建议在Python中采用Ruby风格的块,Guido发布了回复,没有否定它(不确定是否有任何后续的否定):
让我向您展示一个光荣而可怕的黑客技巧:
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]
我在一些项目中犯了这个肮脏的技巧,它有点简单:
lambda args...:( expr1, expr2, expr3, ...,
exprN, returnExpr)[-1]
我希望你能找到一种保持Python风格的方法,但如果你必须这样做,那么使用exec和操作全局变量的方式会比这种方法更加痛苦。
def multiline(*expressions): return expressions[-1]
,那么你可以写成 lambda args...: multiline(expr1, ..., returnExpr)
,这样读起来会更加自然。 - user118967[编辑编辑]由于这个问题在12年后仍然活跃,所以我将继续传统,每隔4年左右修订一次我的答案。
首先,问题是关于如何处理Python中的多行Lambda函数。被接受的答案展示了一个简单的例子。我下面链接的高评分答案回答了“为什么它不是Python的一部分”的问题——这个答案对那些认为现有的“冲突”示例不足以使多行Lambda函数不可能在Python中实现的人可能更加满意。
在此答案的以前版本中,我曾经讨论过如何按原样将多行Lambda函数实现到Python中。但是,我已经将那部分删除,因为它是一堆不良做法。如果您愿意,可以在此答案的编辑历史记录中看到它。
然而,“为什么不呢?”的答案是“因为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将是一个特殊情况(通过颜色后面没有立即换行符标识),它的行为与现在相同。
[编辑] 阅读这个答案。 它解释了为什么多行lambda不可行。
简而言之,这是不符合Python风格的。根据Guido van Rossum的博客文章:
我发现任何将缩进基础块嵌入表达式中间的解决方案都是不可接受的。由于我认为语句分组的替代语法(例如大括号或begin/end关键字)也同样不可接受,这几乎使得多行lambda成为一个无法解决的难题。
(let ([x_ x] [y_ y])
(do-sth-with-x-&-y x_ y_))
是相同的意思
((lambda (x_ y_)
(do-sth-with-x-&-y x_ y_)) x y)
def f(x):
y = f1(x)
z = f2(x, y)
return y,z
f = lambda x: (lambda y: (y, f2(x,y)))(f1(x))
如果你不喜欢数据在操作后被写入,你甚至可以创建let
函数。而且你甚至可以将其柯里化(出于更多的括号的考虑 :)
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)))
到目前为止还不错。但是如果我们需要重新分配任务,即改变状态怎么办?嗯,我认为只要任务不涉及循环,我们就可以完全愉快地生活,不必改变状态。
虽然没有循环的直接lambda替代方案,但我相信我们可以编写相当通用的函数来满足我们的需求。看一下这个斐波那契函数:
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
显然,使用 lambda 函数是不可能的。但是,写一个简单而实用的函数之后,我们就可以解决这个问题和类似的情况:
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]
当然,如果可能的话,应该始终考虑使用map
、reduce
和其他高阶函数。
对于这种问题,似乎一般的方法是利用惰性求值,用接受零参数的 lambda 表达式替换代码块:
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
当然,这并不是完全替代try-except语句的方案,但你总是可以把它变得更通用。顺便说一下,有了这种方法,您甚至可以使if
像函数一样运行!
总结一下:所有提到的东西都感觉有点不自然和不太Python风格美丽。尽管如此- 它确实可以工作!而且没有任何eval
和其他技巧,因此所有的智能提示都将起作用。我也不是声称您应该在每个地方都使用它。大多数情况下,最好定义一个普通函数。我只是想证明没有什么是不可能的。
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]
lambda x: (
y := x ** 2,
[y := y + x for _ in range(10)] if x > 5 else None,
y
)[-1]
循环语句
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]
或者
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]
或者
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]
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是一个多行lambda函数,返回值为y*z。这样行得通吗?
map(func, [1,2,3])
,而底部的应该是一个错误,因为map函数没有足够的参数。此外,代码中有一些额外的括号。 - Samie Bencherif
def
不能与它要使用的逻辑一起排成一行外:您必须将其放在其他地方,然后读者必须去寻找它。对于仅使用一次的代码而言,拥有一个def
是Python语言的严重缺陷:这些仅应该用于代码重用。 - WestCoastProjects