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

我听说在Python中不能添加多行lambdas,因为它们会在语法上与Python中的其他语法结构冲突。今天在公交车上,我一直在思考这个问题,并意识到我想不出任何一个Python构造可以与多行lambdas相冲突。考虑到我对这门语言相当熟悉,这让我很惊讶。

现在,我相信Guido没有在语言中包含多行lambda是有原因的,但出于好奇:在什么情况下,包含多行lambda会有歧义?我听说的是真的吗,还是有其他原因导致Python不允许多行lambda ?

277559 次浏览

看看下面这些:

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

这是一个返回(y, [1,2,3])的lambda(因此映射只得到一个参数,导致错误)?还是返回y?或者这是语法错误,因为新行上的逗号放错了地方?巨蟒怎么知道你想要什么?

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

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

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

“但对我来说,这个谜题的任何解决方案的复杂性都是巨大的:它要求解析器(或者更准确地说,词法分析器)能够在缩进敏感和不缩进模式之间来回切换,保持先前模式的堆栈和缩进水平。从技术上讲,这些问题都可以解决(已经有一堆缩进级别可以被一般化)。但这些都不能改变我的直觉,这一切都是精心设计的鲁布·戈德堡的精巧装置。”

以下是几个相关的连结:

有一段时间,我一直在跟踪Reia的开发,它最初也将在Erlang之上使用Python的基于缩进的语法和Ruby块。但是,设计师最终放弃了缩进敏感性,他写的这篇文章包括了他在缩进+多行块中遇到的问题的讨论,以及他对Guido的设计问题/决策的更多欣赏:

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

另外,这里有一个关于ruby风格的Python块的有趣建议,我遇到过Guido发布了一个响应,实际上没有将它击落(虽然不确定是否有任何后续的击落):

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

这通常是非常丑陋的(但有时替代方案甚至更丑陋),所以一个变通的方法是创建一个大括号表达式:

lambda: (
doFoo('abc'),
doBar(123),
doBaz())
不过它不会接受任何赋值,所以你必须事先准备好数据。 我发现这种方法很有用的地方是PySide包装器,在那里有时会有简短的回调。编写额外的成员函数将更加丑陋。通常你不需要这个。

例子:

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

让我试着解决@balpha解析问题。我会用圆括号括住多行。如果没有括号,lambda定义是贪婪的。所以in

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。这有用吗?

[编辑编辑]因为这个问题在被问到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的一部分——但这是一个快速的说明,也许是可能的,在语言的一些改变

[Edit] Read 这个答案。它解释了为什么多行lambda不是一个东西。

简单地说,它是非python的。Guido van Rossum在博客中写道:

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

(对于仍然对这个话题感兴趣的人。)

考虑一下这一点(甚至包括在“multiline”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

让我给你介绍一个光荣但可怕的技巧:

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]

关于丑陋的黑客,你总是可以使用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')

我很内疚在我的一些更简单的项目中实践了这个肮脏的hack:

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

我希望你能找到一种方法保持python化,但如果你必须这样做,这比使用exec和操纵全局变量要少一些痛苦。

我只是玩了一点,试图用reduce来理解字典,并想出了这个一行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}

我只是试图做同样的事情,在这个Javascript字典理解:https://stackoverflow.com/a/11068265

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

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

例子:

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


Output: 30

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

人为的例子,乘法表达式(x*2)被提取为函数,因此我可以使用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))

< a href = " https://repl。it/@datracka/python-lambda-function" rel="nofollow noreferrer">https://repl.it/@datracka/python-lambda-function .it/@datracka/python-lambda-function " rel="nofollow noreferrer">https://repl.it/

也许它并没有确切地回答这个问题,如果那是如何在lambda表达式本身做多行,但如果有人得到这个线程,看看如何调试表达式(像我一样),我认为它会有帮助

下面是一个更有趣的多行lambdas实现。这是不可能实现的,因为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问题的解决方案,其中一个调用的结果被另一个调用消耗。

我希望它不是超级hack,因为它只是基于标准库函数,没有使用dunder方法。

下面是一个简单的例子,我们从x = 3开始,然后在第一行添加1,然后在第二行添加2,并得到6作为输出。

from functools import reduce


reduce(lambda data, func: func(data), [
lambda x: x + 1,
lambda x: x + 2
], 3)


## Output: 6

让我也说说我对不同变通方法的看法。

一个简单的单行lambda与普通函数有什么不同?我能想到的只有缺少赋值,一些类似循环的结构(for, while), try-except子句……就这样?我们甚至有一个三元运算符——酷!那么,让我们试着解决每一个问题。

作业

这里有些人正确地指出,我们应该看看lisp的let表单,它允许本地绑定。实际上,所有不改变状态的赋值只能用let来执行。但是每个lisp程序员都知道let形式绝对等价于调用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

lambda版本如下:

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

显然,用是不可能的。但在写了一个小而有用的函数之后,我们就完成了这个和类似的情况:

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]

当然,如果可能的话,应该总是考虑使用mapreduce和其他高阶函数。

Try-except和其他控制结构

解决这类问题的一般方法似乎是使用惰性求值,用不接受参数的lambdas替换代码块:

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表现得像函数!

总结一下:这是很自然的,所有提到的东西都感觉有点不自然,不那么美丽。尽管如此,它还是有效的!并且没有任何evals和其他trics,所以所有的智能感知都将工作。我也不是说你应该在任何地方都使用它。通常你最好定义一个普通的函数。我只是证明了没有什么是不可能的。

在Python 3.8/3.9中有赋值表达式,所以它可以在lambda中使用 扩大功能< / p >

例如,代码

#%%
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项之间传递任意数量的变量的一个安全方法:

print((lambda: [
locals().__setitem__("a", 1),
locals().__setitem__("b", 2),
locals().__setitem__("c", 3),
locals().get("a") + locals().get("b") + locals().get("c")
])()[-1])

输出:6

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

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]

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]