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

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


当前回答

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

其他回答

我知道这是一个老问题,但是为了记录,这里有一种多行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函数应该是单行的,作为函数的最简单形式,一个入口,然后返回

[编辑编辑]因为这个问题在被问到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成为一个无法解决的难题。

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

一个简单的单行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]

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

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

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

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