我在理解Python如何在运行时处理和评估lambda时遇到了一些麻烦。
考虑以下代码(Python 3.5.2):
x = 0
for iteration in range(3):
x = x + 1
print(x)
正如所料,这打印3.这是我对x改变3次迭代的方式的理解:
x
x+1
(x+1) + 1
((x+1) + 1) + 1
请考虑以下代码:
add3 = lambda x: x
for iteration in range(3):
add3 = lambda x: add3(x) + 1
print(add3(0))
以下是我对add3 应该更改3次迭代的方式的理解:
lambda x: x
lambda x: (lambda x: x)(x) + 1
lambda x: (lambda x: (lambda x: x)(x) + 1)(x) + 1
lambda x: (lambda x: (lambda x: (lambda x: x)(x) + 1)(x) + 1)(x) + 1
相反,调用add3会导致超出最大递归深度。 我的第一个想法是Python在调用时从名称动态查找函数体,而不是将函数的代码存储为函数的一部分。但是,即使以下代码也不起作用:
functionList = [lambda x: x] #Store each iteration separately
for i in range(3):
oldFunction = functionList[-1]
newFunction = lambda x: oldFunction(x) + 1 #Should be a completely new lambda object
functionList.append(newFunction)
print(functionList[-1](0)) #Causes infinite recursion
即使没有任何命名功能,并遵循建议here(虽然我可能误解了他的答案),但它仍然失败:
functionList = [lambda x: x]
for i in range(3):
functionList.append(lambda x, i=i: functionList[-1](x) + 1)
print(functionList[-1](0)) #Causes infinite recursion
functionList中包含的四个lambda是内存中完全独立的对象:
>>> print(functionList)
[<function <lambda> at 0x00000266D41A12F0>, <function <lambda> at 0x00000266D41D7E18>, <function <lambda> at 0x00000266D41D7730>, <function <lambda> at 0x00000266D41D7840>]
有人可以解释一下这种行为吗?
答案 0 :(得分:1)
此行为与&#39; iterational&#39;无关。 lambda代。当您说add3 = lambda x: add3(x) + 1
时,add3
对象被替换,其中lambda 以递归方式调用自身而没有终止条件。
因此,当您致电add3(0)
时,它会变为:
add3(0) = add3(0) + 1 = (add3(0) + 1) + 1 = ((add3(0) + 1) + 1) + 1
这将永远持续下去(好吧,直到超过最大递归深度)。
对于其他示例,列表中的第二个功能已失败并显示RecursionError: maximum recursion depth exceeded
。
我已经为您准备了此代码:
import copy
flist=[lambda x: x]
flist.append(lambda x: copy.deepcopy(flist[-1])(x) + 1)
>>> flist
[<function <lambda> at 0x101d45268>, <function <lambda> at 0x101bf1a60>]
因此我们确保调用函数的副本。 flist[-1](0)
会产生RecursionError
,并在deepcopy
模块中引发错误。因此,这意味着copy.deepcopy(flist[-1])(x)
表示复制列表中的最后一个元素 并运行副本&#39;。
这就是:列表的最后一个元素一遍又一遍地调用它。
答案 1 :(得分:0)
在运行时评估它是正确的。因此,add3
在自身引用时会调用自身,而不是旧的add3
。对于循环,您始终使用[-1]
。由于它是在运行时进行评估的,因此第一个调用列表末尾的那个。然后列表末尾的那个调用列表末尾的那个调用列表末尾的那个...列表在调用函数时没有改变。因此,第一个函数被调用一次,然后最后一个函数被无限调用。您想要的是使用[i]
而不是[-1]
。在lambdas中使用i
的默认参数是非常好的,因为默认参数是在定义时计算的。
答案 2 :(得分:0)
Python允许您访问封闭范围中的变量,但是您在循环期间更改这些变量。
add3 = lambda x: x
for iteration in range(3):
# This add3 will call itself!
# It always uses the *current* value of add3,
# NOT the value of add3 when it was created.
add3 = lambda x: add3(x) + 1
print(add3(0))
您需要创建一个新的绑定:
add3 = lambda x: x
for _ in range(3):
add3 = (lambda f: lambda x: f(x) + 1)(add3)
print(add3(0))
每次循环都会创建一个新范围,这样您就可以使用add3
的当前值而不是add3
的未来值进行绑定。
与您描述的方式相似的语言有点罕见。 Haskell就是这种语言的一个例子。在C ++中,您可以通过使用[=]
隐式复制lambdas来捕获列表来实现此目的。如果没有一些额外的工作,Python就不会这样做。
答案 3 :(得分:0)
以下是add3
在整个循环中实际变化的方式:
lambda x: x
lambda x: add3(x) + 1
add3
不会立即替换为lambda体内!执行该函数时会查找它。
lambda x: add3(x) + 1
再次lambda x: add3(x) + 1
最后,当你调用add3
时,lambda体内对add3
的递归调用会查找add3
的当前值,而不是定义lambda时的值。 add3
调用自身,调用自身,调用自身,依此类推,直到堆栈溢出。
您尝试的修复无效,因为oldFunction
仍然在功能执行时被查找,因此它仍会找到最终oldFunction
而不是oldFunction
从函数定义时间。你没有做任何改变。
为了进行比较,当您执行x = x + 1
时,x
会立即替换,因此在第一个版本中,x
的值会相继{{1} },0
,1
和2
。你最后没有3
;你有x = ((x+1) + 1) + 1
。
答案 4 :(得分:0)
除名称和名称绑定外,表达式lambda <args>: <expression>
创建的函数等于def f(<args>): return <expression>
的结果。语句f = lambda <args>: <expression>
甚至没有名称绑定差异。然而,有些人错误地假设其他差异。许多与lambda的难题通过用等效的defs替换它来照亮。对于上面的代码,我们得到以下内容。
def add3(x): return x
for iteration in range(3):
def add3(x): return add3(x) + 1
print(add3(0))
通过将add3
重新绑定到新的函数对象,这是否更明显第二个def否定了第一个?并且不止一次执行第二个def语句是多余的?没有终端基本情况的递归函数来阻止它会引发递归错误,这仍然是一个惊喜吗?