范围变量和循环

时间:2018-06-28 13:22:13

标签: python

我在Python的官方手册中读了一句话。

  

如果在函数内定义的lambda或def嵌套在循环内,并且>嵌套函数引用该循环所更改的封闭范围变量,则循环内生成的所有函数将具有相同的值-该值>所引用的变量在上一次循环迭代中具有。在这种情况下,您必须>仍然使用默认值来保存变量的当前值。

关于脚本:

def makeActions():
     acts=[]
     for i in range(5):
         acts.append(lambda x: i**x)
     return acts
acts=makeActions()
print(acts)

我知道问题的外观以及解决方法。但是我就是无法理解原因。 请看看我的解释:

def makeActions():
    acts=[]
    for i in range(5):
        acts.append(lambda x: i**x) #step2-step6: each lambda would be stored 
                                    #in the list, however, with its own "i"
    return acts

acts=makeActions() #step1: when the function is called, the list would be 
                   #created. And lambda would be called as well.
print(acts)

我的观点是,在每次迭代中,即使未给出参数x也会调用每个lambda。因此,在“行为”列表中,每个元素(即每个lambda)具有不同的“ i”。

我知道我错了,但是你能告诉我为什么吗? 谢谢!

2 个答案:

答案 0 :(得分:4)

为了进一步说明这个问题,对i的引用在lambda中没有改变-您看到i的每次迭代都会生成一个新对象:

def makeActions():
  acts=[]
  for i in range(5):
    print('i:', i, 'object id:', id(i))
    acts.append(lambda: id(i))
  print('final i:', i, 'object id:', id(i))
  return acts
acts=makeActions()
print([fn() for fn in acts])

返回

i: 0 object id: 140418781832928
i: 1 object id: 140418781832960
i: 2 object id: 140418781832992
i: 3 object id: 140418781833024
i: 4 object id: 140418781833056
final i: 4 object id: 140418781833056
[140418781833056, 140418781833056, 140418781833056, 140418781833056, 140418781833056]

i的引用始终指向其最后分配的对象,因此i的ID在最后一次更改后将不会更改。

如果要在创建lambda时保留值,则需要在所需的位置和时间“捕获”引用的值,例如,可以将lambda创建委托给一个函数:

def delegated(d):
  return lambda: id(d)

def makeDelegatedActions():
  acts=[]
  for i in range(5):
    print('i:', i, 'object id:', id(i))
    acts.append(delegated(i))
  print('final i:', i, 'object id:', id(i))
  return acts
acts=makeDelegatedActions()
print([fn() for fn in acts])

返回哪个

i: 0 object id: 140418781832928
i: 1 object id: 140418781832960
i: 2 object id: 140418781832992
i: 3 object id: 140418781833024
i: 4 object id: 140418781833056
final i: 4 object id: 140418781833056
[140418781832928, 140418781832960, 140418781832992, 140418781833024, 140418781833056]

Online demo here

答案 1 :(得分:2)

每个lambda只会知道从其封闭范围(即i调用)中提取makeActions。他们实际上并没有这么做,直到他们自称。 i是同一作用域内的相同名称,它恰好在循环期间采用不同的值,并且仅在循环完成后保留最后一个值。因此,lambda最终创建了5个相同的函数。

可行的变体形式

def expgen(n):
    def myexp(x):
        return n**x
    return myexp

acts = [expgen(i) for i in range(5)]

在这种情况下,获取n的范围是expgen的范围,它在列表理解中具有不同的调用,并且每个函数将独立工作。