为什么来自 classdef 套件的名称范围仅限于从 funcdef 的闭包中排除?

Why scope of names from a classdef suite is limited to be discluded from closure of a funcdef?

我期待 funcdef 将最近的内部闭包绑定到它的定义。显然不是这样:

phoo = 4
class Alice: # 'classdef'
  # <class 'suite'>:
  phoo = 1
  spam = phoo + 11
  blah = staticmethod(lambda: phoo + 22)
  @staticmethod
  def Blake():
    return phoo + 33

测试:

>>> Alice.spam
12
>>> Alice.blah()
26
>>> Alice.Blake()
37

据说a code block is executed execution frame。在 class 定义的 'block' run/executed 时,spamAlice.

内部解析 phoo

我希望 Blake 内部的解决方案能够从 Alice 解决 phooexecution model 表示,

If the definition occurs in a function block, the scope extends to any blocks contained within the defining one, unless a contained block introduces a different binding for the name.

然后它说,

It is said that a code block is executed execution frame.

这个决定导致我的假设出错。背后的原理是什么?

编辑: 这是 python 2 个旧式 classes;但如果注明的答案可以在新式 classes 上。我问了原因,不过如果你能加上内幕技术说明,也非常欢迎!

从直观的角度来看,答案很简单:

函数定义中的自由变量捕获封闭范围内的变量。但是 class 属性不是变量,它们是 class 属性;您必须以 Alice.spamself.spam 而非 spam 的形式访问它们。因此,spam 不会捕获外部 spam 因为没有外部 spam.


但在幕后,这不是真的。

对于新式class,在执行class定义体时,spam实际上中的局部变量该机构的范围;只有当元class(在本例中为type)被执行时,class 属性才会从这些局部变量中创建。[1]

对于旧式 class,它没有完全定义会发生什么,因此您几乎必须求助于实现。特别是,没有使用 class 定义的局部变量执行 metaclass 来生成 class 对象的步骤。但在大多数情况下,它的工作原理就好像是这样。


那么,为什么 spam 绑定到那个本地?

自由变量只能从外部范围绑定到闭包单元,这是一种特殊的局部变量。当局部函数访问它时,编译器只为函数定义中的变量创建一个闭包单元。它不会为 class 定义中的变量创建闭包单元。


那么,如果 spam 不绑定到 Alice.spam,那么 绑定到 什么?好吧,根据通常的 LEGB 规则,如果没有局部赋值,也没有封闭的单元格变量,那么它就是全局的。


如果没有例子,上面的一些内容可能很难理解,所以:

>>> def f():
...     a=1
...     b=2
...     def g():
...         b
...     return g
>>> f.__code__.co_cellvars # cell locals, captured by closures
('b',)
>>> f.__code__.co_varnames # normal locals
('a', 'g')
>>> g = f()
>>> g.__code__.co_freevars # free variables that captured cells
('b',)
>>> class Alice:
...    a=1
...    b=2
...    def f():
...        b
>>> Alice.f.__func__.__code__.co_freevars
()
>>> Alice.f.__func__.__code__.co_varnames
()
>>> Alice.f.__func__.__code__.co_names # loosely, globals
('b',)

如果您想知道 co_cellvars 之类的内容在哪里指定……好吧,它们没有,但是 inspect 模块文档对它们的含义进行了简要总结。


如果您了解 CPython 字节码,也值得对所有这些代码块调用 dis 以查看用于加载和保存所有这些变量的指令。


所以,最大的问题是,为什么 Python 为 class 定义生成单元格?

除非 Guido 记得,并且觉得它足够有趣来写一个 Python 历史博客 post,否则我不确定我们是否会知道答案。 (当然,你可以试着问他——在他的博客上发表评论或向任何一个看起来最相关的邮件列表发送电子邮件可能是最好的方法。)

但这是我的猜测:

单元格被实现为存储在代码对象中的数组的索引。当函数被调用时,它的框架得到一个匹配的对象数组。当在该函数调用内执行局部函数定义时,自由变量将绑定到对框架中单元槽的引用。

类 没有 __code__ 成员(或者,pre-2.6,co_code)。为什么?因为一个 class 定义一旦定义就执行,并且再也不会执行,所以何必呢?这意味着没有地方可以存放细胞,也没有任何东西可供参考。最重要的是,执行框架总是在执行完成后立即消失,因为不能有任何外部引用。

当然,您 可以 改变它:将 __code__ 成员添加到 classes,在其中创建单元格,然后,如果有人关闭了这些单元格单元格,这将使框架在执行后保持活动状态,就像它对函数所做的那样。那是个好主意吗?我不知道。我的猜测是当 Python classes 首次被定义时没有人问这个问题。虽然现在很明显 class 定义和函数定义有多少共同点,但我认为这是 Guido 时间机器的一个例子——他做出设计决定时没有意识到它会解决十年前没人提出的问题稍后。


[1] 其中一些细节可能是 CPython 特定的。例如,我认为实现为函数中的 every 局部创建闭包单元或使用其他等效机制在技术上是合法的。例如,如果你在内部函数中执行 exec('spam=3'),所有语言参考都说它不能保证它会影响外部函数的 spam,而不是保证不会这样做。