类范围内的闭包

Posted

技术标签:

【中文标题】类范围内的闭包【英文标题】:Closures in a class scope 【发布时间】:2012-11-03 03:04:59 【问题描述】:

据我了解,函数和类作用域的行为几乎相同:

>>> def x():
...     a = 123
...     print (locals())
... 
>>> x()
'a': 123


>>> class x():
...     a = 123
...     print (locals())
... 
'a': 123, '__module__': '__main__'

然而,当我定义一个闭包时,行为是不同的。正如预期的那样,函数只是返回本地绑定:

>>> def x():
...     a = 123
...     t = lambda: a
...     return t
... 
>>> x()()
123

而在一个类中,绑定似乎丢失了:

>>> class x():
...     a = 123
...     t = lambda self: a
... 
>>> x().t()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 3, in <lambda>
NameError: global name 'a' is not defined

谁能解释这个差异?

【问题讨论】:

【参考方案1】:

类作用域是一个临时作用域,它只在执行类定义主体时存在。生成的dict 用于创建类命名空间,即类的__dict__

就类中定义的函数而言,下一个“向上”范围是class 定义本身的范围。

以下工作正常:

>>> def foo():
...     spam = 'eggs'
...     class Bar(object):
...         def baz(self): return spam
...     return Bar()
... 
>>> foo().baz()
'eggs'

这记录在pep 227:

类范围内的名称不可访问。名称在 最里面的封闭函数范围。如果一个类定义 发生在嵌套范围链中,解析过程跳过 类定义。

class compound statement documentation:

然后使用新创建的本地命名空间和原始全局命名空间在新的执行框架中执行该类的套件(参见Naming and binding 部分)。 (通常,套件只包含函数定义。)当类的套件完成执行时,其执行帧被丢弃,但其本地命名空间被保存。 [4] 然后使用基类的继承列表和属性字典的保存的本地命名空间创建一个类对象。

强调我的;执行框架是临时作用域。

【讨论】:

类主体执行完毕后,作用域内的所有内容都被塞入类__dict__,然后被删除。所以当你运行ta没有被绑定。 @katrielalex:没错,这就是我对used to create the class namespace的意思。 @katrielalex:请注意,a 被绑定只是意味着 a 范围在它的 locals() 字典中。 globals() 只是模块 locals(),真的。 我不知道你所说的“临时”作用域是什么意思(是否有“永久”作用域?),但目前t = lambda: a 正在执行,a 确实存在。所以闭包知道这个绑定,但后来不知何故丢失了它。 @CS:我没有说这是技术原因,我只是说类范围应该被视为临时的。当然,您可以为包含的函数对象提供闭包来访问类属性,但这会导致很多范围界定混乱。类命名空间只能通过显式引用访问(通过type(self).class_attrClassName.class_attr 或通过self.class_attr,前提是实例上没有掩码名称)。

以上是关于类范围内的闭包的主要内容,如果未能解决你的问题,请参考以下文章

JavaScript中的“闭包”

浅谈JavaScript闭包

实际场景中的代表:在命名空间范围内的类范围内

整个类范围内的 C++ 命名空间别名

类范围内的动态实例变量

Java生成指定范围内的工具类