Python - 迭代生成的lambda不起作用

时间:2016-07-09 20:39:40

标签: python lambda

我在理解Python如何在运行时处理和评估lambda时遇到了一些麻烦。

迭代地构建一个整数

考虑以下代码(Python 3.5.2):

x = 0
for iteration in range(3):
    x = x + 1
print(x)

正如所料,这打印3.这是我对x改变3次迭代的方式的理解:

  • 初始值: x
  • 迭代1: x+1
  • 迭代2: (x+1) + 1
  • 迭代3: ((x+1) + 1) + 1

迭代地建立一个lambda

请考虑以下代码:

add3 = lambda x: x
for iteration in range(3):
    add3 = lambda x: add3(x) + 1
print(add3(0))

以下是我对add3 应该更改3次迭代的方式的理解:

  • 初始值: lambda x: x
  • 迭代1: lambda x: (lambda x: x)(x) + 1
  • 迭代2: lambda x: (lambda x: (lambda x: x)(x) + 1)(x) + 1
  • 迭代3: 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>]

有人可以解释一下这种行为吗?

5 个答案:

答案 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
  • 迭代1: lambda x: add3(x) + 1

add3不会立即替换为lambda体内!执行该函数时会查找它。

  • 迭代2: lambda x: add3(x) + 1再次
  • 迭代3:仍为lambda x: add3(x) + 1

最后,当你调用add3时,lambda体内对add3的递归调用会查找add3当前值,而不是定义lambda时的值。 add3调用自身,调用自身,调用自身,依此类推,直到堆栈溢出。

您尝试的修复无效,因为oldFunction 仍然在功能执行时被查找,因此它仍会找到最终oldFunction而不是oldFunction从函数定义时间。你没有做任何改变。

为了进行比较,当您执行x = x + 1时,x 会立即替换,因此在第一个版本中,x的值会相继{{1} },012。你最后没有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语句是多余的?没有终端基本情况的递归函数来阻止它会引发递归错误,这仍然是一个惊喜吗?