嵌套函数中的局部变量

时间:2012-09-14 11:28:11

标签: python scope closures nested-function

好的,请相信我,我知道它会让人感到非常复杂,但请帮助我理解发生了什么。

from functools import partial

class Cage(object):
    def __init__(self, animal):
        self.animal = animal

def gotimes(do_the_petting):
    do_the_petting()

def get_petters():
    for animal in ['cow', 'dog', 'cat']:
        cage = Cage(animal)

        def pet_function():
            print "Mary pets the " + cage.animal + "."

        yield (animal, partial(gotimes, pet_function))

funs = list(get_petters())

for name, f in funs:
    print name + ":", 
    f()

给出:

cow: Mary pets the cat.
dog: Mary pets the cat.
cat: Mary pets the cat.

基本上,为什么我没有得到三种不同的动物?不是cage'打包的'进入嵌套函数的局部范围?如果没有,对嵌套函数的调用如何查找局部变量?

我知道遇到这些问题通常意味着一个人做错了,但我想了解会发生什么。

3 个答案:

答案 0 :(得分:103)

嵌套函数在执行时从父作用域中查找变量,而不是在定义时查找。

编译函数体,验证'free'变量(未通过赋值在函数本身中定义),然后将闭包单元绑定到函数,代码使用索引引用每个单元格。 pet_function因此有一个自由变量(cage),然后通过闭包单元引用索引0.闭包本身指向本地变量cage in get_petters函数。

当您实际调用该函数时,该闭包将用于在您调用函数时查看周围范围cage的值。这就是问题所在。当您调用函数时,get_petters函数已经完成计算结果。在执行过程中的某个时刻cage局部变量被分配了'cow''dog''cat'字符串,但在函数末尾{{1}包含最后一个值cage。因此,当您调用每个动态返回的函数时,您将获得值'cat'打印。

解决方法是不依赖于闭包。您可以使用部分功能,创建新功能范围,或将变量绑定为关键字参数的默认值

  • 部分功能示例,使用functools.partial()

    'cat'
  • 创建新范围示例:

    from functools import partial
    
    def pet_function(cage=None):
        print "Mary pets the " + cage.animal + "."
    
    yield (animal, partial(gotimes, partial(pet_function, cage=cage)))
    
  • 将变量绑定为关键字参数的默认值:

    def scoped_cage(cage=None):
        def pet_function():
            print "Mary pets the " + cage.animal + "."
        return pet_function
    
    yield (animal, partial(gotimes, scoped_cage(cage)))
    

没有必要在循环中定义def pet_function(cage=cage): print "Mary pets the " + cage.animal + "." yield (animal, partial(gotimes, pet_function)) 函数,编译只发生一次,而不是循环的每次迭代。

答案 1 :(得分:12)

我的理解是,当实际调用屈服的pet_function时,在父函数命名空间中查找cage,而不是之前。

所以当你这样做时

funs = list(get_petters())

您生成3个函数,可以找到最后创建的笼子。

如果用以下代码替换上一个循环:

for name, f in get_petters():
    print name + ":", 
    f()

你真的会得到:

cow: Mary pets the cow.
dog: Mary pets the dog.
cat: Mary pets the cat.

答案 2 :(得分:6)

这源于以下

for i in range(2): 
    pass

print i is 1

在迭代后,i的值被懒惰地存储为其最终值。

作为一个生成器,该函数可以工作(即依次打印每个值),但转换为在生成器上运行的列表时,因此所有调用cage({{ 1}})返回猫。