我听说在Python中不能添加多行lambdas,因为它们会在语法上与Python中的其他语法结构冲突。今天在公交车上,我一直在思考这个问题,并意识到我想不出任何一个Python构造可以与多行lambdas相冲突。考虑到我对这门语言相当熟悉,这让我很惊讶。
现在,我相信Guido没有在语言中包含多行lambda是有原因的,但出于好奇:在什么情况下,包含多行lambda会有歧义?我听说的是真的吗,还是有其他原因导致Python不允许多行lambda ?
我听说在Python中不能添加多行lambdas,因为它们会在语法上与Python中的其他语法结构冲突。今天在公交车上,我一直在思考这个问题,并意识到我想不出任何一个Python构造可以与多行lambdas相冲突。考虑到我对这门语言相当熟悉,这让我很惊讶。
现在,我相信Guido没有在语言中包含多行lambda是有原因的,但出于好奇:在什么情况下,包含多行lambda会有歧义?我听说的是真的吗,还是有其他原因导致Python不允许多行lambda ?
当前回答
在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
其他回答
下面是一个更有趣的多行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)
这将返回一个标量数字列表,因此可以使用它将多个值减少为一个。
有那么多肯定不是很有效但是如果你有约束的话,这是一个快速完成一些事情的好方法然后再把它重写成一个实际的函数。
看看下面这些:
map(multilambda x:
y=x+1
return y
, [1,2,3])
这是一个lambda返回(y,[1,2,3])(因此映射只得到一个参数,导致错误)?还是返回y?或者这是语法错误,因为新行上的逗号放错了地方?巨蟒怎么知道你想要什么?
在paren中,缩进对python来说并不重要,因此不能明确地使用多行。
这只是一个简单的例子,可能还有更多的例子。
让我也说说我对不同变通方法的看法。
一个简单的单行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表现得像函数!
总结一下:这是很自然的,所有提到的东西都感觉有点不自然,不那么美丽。尽管如此,它还是有效的!没有任何评价和其他技巧,所以所有的智能感知将工作。我也不是说你应该在任何地方都使用它。通常你最好定义一个普通的函数。我只是证明了没有什么是不可能的。
我很内疚在我的一些更简单的项目中实践了这个肮脏的hack:
lambda args...:( expr1, expr2, expr3, ...,
exprN, returnExpr)[-1]
我希望你能找到一种方法保持python化,但如果你必须这样做,这比使用exec和操纵全局变量要少一些痛苦。
Guido van Rossum (Python的发明者)在一篇旧博客文章中亲自回答了这个问题。 基本上,他承认这在理论上是可能的,但任何提出的解决方案都是非python的:
“但对我来说,这个谜题的任何解决方案的复杂性都是巨大的:它要求解析器(或者更准确地说,词法分析器)能够在缩进敏感和不缩进模式之间来回切换,保持先前模式的堆栈和缩进水平。从技术上讲,这些问题都可以解决(已经有一堆缩进级别可以被一般化)。但这些都不能改变我的直觉,那就是这一切都是一个精心设计的鲁布·戈德堡(Rube Goldberg)的精巧装置。”