嵌套函数中的局部变量

2024-11-21 08:34:00
admin
原创
201
摘要:问题描述:好的,请耐心听我说完,我知道这看起来非常复杂,但请帮助我理解发生了什么。from functools import partial class Cage(object): def __init__(self, animal): self.animal = animal d...

问题描述:

好的,请耐心听我说完,我知道这看起来非常复杂,但请帮助我理解发生了什么。

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“打包”不是进入嵌套函数的本地范围吗?如果不是,那么对嵌套函数的调用如何查找局部变量?

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


解决方案 1:

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

函数体被编译,并且“自由”变量(未通过赋值在函数本身中定义)被验证,然后作为闭包单元绑定到函数,代码使用索引来引用每个单元。pet_function因此有一个自由变量(cage),然后通过闭包单元索引 0 进行引用。闭包本身指向函数cage中的局部变量get_petters

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

解决方法是不依赖闭包。你可以改用部分函数,​​创建新的函数作用域,或将变量绑定为关键字参数的默认值

  • 部分函数示例,使用functools.partial()

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))

不需要scoped_cage在循环中定义函数,编译只进行一次,而不是在循环的每次迭代中都进行。

解决方案 2:

我的理解是,当实际调用产生的 pet_function 时,会在父函数命名空间中寻找 holder ,而不是之前。

所以当你这样做

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.

解决方案 3:

这源于以下原因

for i in range(2): 
    pass

print(i)  # prints 1

迭代之后,的值i被延迟存储为其最终值。

作为生成器,该函数可以工作(即依次打印每个值),但是当转换为列表时,它会在生成器上运行cage,因此对( ) 的所有调用cage.animal都会返回 cats。

解决方案 4:

让我们简化这个问题。定义:

def get_petters():
    for animal in ['cow', 'dog', 'cat']:
        def pet_function():
            return "Mary pets the " + animal + "."

        yield (animal, pet_function)

然后,就像问题一样,我们得到:

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

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

但如果我们避免创造list()第一:

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

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

发生了什么事?为什么这种细微的差别会彻底改变我们的结果?


如果我们看一下list(get_petters()),从变化的内存地址可以清楚地看出,我们确实产生了三个不同的函数:

>>> list(get_petters())

[('cow', <function get_petters.<locals>.pet_function at 0x7ff2b988d790>),
 ('dog', <function get_petters.<locals>.pet_function at 0x7ff2c18f51f0>),
 ('cat', <function get_petters.<locals>.pet_function at 0x7ff2c14a9f70>)]

但是,看一下cell这些函数所绑定的 s:

>>> for _, f in list(get_petters()):
...     print(f(), f.__closure__)

Mary pets the cat. (<cell at 0x7ff2c112a9d0: str object at 0x7ff2c3f437f0>,)
Mary pets the cat. (<cell at 0x7ff2c112a9d0: str object at 0x7ff2c3f437f0>,)
Mary pets the cat. (<cell at 0x7ff2c112a9d0: str object at 0x7ff2c3f437f0>,)

>>> for _, f in get_petters():
...     print(f(), f.__closure__)

Mary pets the cow. (<cell at 0x7ff2b86b5d00: str object at 0x7ff2c1a95670>,)
Mary pets the dog. (<cell at 0x7ff2b86b5d00: str object at 0x7ff2c1a952f0>,)
Mary pets the cat. (<cell at 0x7ff2b86b5d00: str object at 0x7ff2c3f437f0>,)

对于这两个循环,cell对象在整个迭代过程中保持不变。但是,正如预期的那样,str它引用的具体内容在第二个循环中会发生变化。cell对象引用的是animal,它是在get_petters()调用时创建的。但是,在生成器函数运行时,它引用的对象animal会发生变化。str

在第一个循环中,在每次迭代期间,我们都会创建所有的fs,但我们只在生成器get_petters()完全用尽并且list已经创建了函数后才调用它们。

在第二个循环中,每次迭代期间,我们都会暂停get_petters()生成器并在每次暂停后调用f。因此,我们最终会检索animal生成器函数暂停时的值。

正如@Claudiu 在回答类似问题时所说:

创建了三个独立的函数,但它们各自都具有定义环境的闭包 - 在本例中为全局环境(如果循环位于另一个函数内,则为外部函数的环境)。然而,这正是问题所在 - 在这个环境中,animal是变异的,并且所有闭包都引用相同的animal

[编者注:i已更改为animal。]

相关推荐
  政府信创国产化的10大政策解读一、信创国产化的背景与意义信创国产化,即信息技术应用创新国产化,是当前中国信息技术领域的一个重要发展方向。其核心在于通过自主研发和创新,实现信息技术应用的自主可控,减少对外部技术的依赖,并规避潜在的技术制裁和风险。随着全球信息技术竞争的加剧,以及某些国家对中国在科技领域的打压,信创国产化显...
工程项目管理   1565  
  为什么项目管理通常仍然耗时且低效?您是否还在反复更新电子表格、淹没在便利贴中并参加每周更新会议?这确实是耗费时间和精力。借助软件工具的帮助,您可以一目了然地全面了解您的项目。如今,国内外有足够多优秀的项目管理软件可以帮助您掌控每个项目。什么是项目管理软件?项目管理软件是广泛行业用于项目规划、资源分配和调度的软件。它使项...
项目管理软件   1354  
  信创国产芯片作为信息技术创新的核心领域,对于推动国家自主可控生态建设具有至关重要的意义。在全球科技竞争日益激烈的背景下,实现信息技术的自主可控,摆脱对国外技术的依赖,已成为保障国家信息安全和产业可持续发展的关键。国产芯片作为信创产业的基石,其发展水平直接影响着整个信创生态的构建与完善。通过不断提升国产芯片的技术实力、产...
国产信创系统   21  
  信创生态建设旨在实现信息技术领域的自主创新和安全可控,涵盖了从硬件到软件的全产业链。随着数字化转型的加速,信创生态建设的重要性日益凸显,它不仅关乎国家的信息安全,更是推动产业升级和经济高质量发展的关键力量。然而,在推进信创生态建设的过程中,面临着诸多复杂且严峻的挑战,需要深入剖析并寻找切实可行的解决方案。技术创新难题技...
信创操作系统   27  
  信创产业作为国家信息技术创新发展的重要领域,对于保障国家信息安全、推动产业升级具有关键意义。而国产芯片作为信创产业的核心基石,其研发进展备受关注。在信创国产芯片的研发征程中,面临着诸多复杂且艰巨的难点,这些难点犹如一道道关卡,阻碍着国产芯片的快速发展。然而,科研人员和相关企业并未退缩,积极探索并提出了一系列切实可行的解...
国产化替代产品目录   28  
热门文章
项目管理软件有哪些?
云禅道AD
禅道项目管理软件

云端的项目管理软件

尊享禅道项目软件收费版功能

无需维护,随时随地协同办公

内置subversion和git源码管理

每天备份,随时转为私有部署

免费试用