Skip to main content
 首页 » 编程设计

python之为什么将classdef套件中的名称范围限制为不包括funcdef的关闭

2024年08月26日33yxwkf

我期望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。在类定义的' block'运行/执行时, spam从 phoo内部解析 Alice。

我期望从 Blake内部解析以从 phoo解析 Alice。 execution model说,


  如果定义发生在功能块中,则范围将扩展到定义块中包含的任何块,除非所包含的块为名称引入了不同的绑定。


然后说


  据说是 a code block is executed execution frame。


这个决定使我的假设错了。其背后的原理是什么?

编辑:这是python 2旧式类;但是如果有注释,答案可以是新式的类。我问了原因,但是如果您可以添加内部技术说明,也非常欢迎!

请您参考如下方法:

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

函数定义中的自由变量将捕获范围内的变量。但是类属性不是变量,它们是类属性。您必须以Alice.spam或self.spam(而不是spam)访问它们。因此,spam不会捕获外部spam,因为没有外部spam。



但实际上,事实并非如此。

对于新型类,在执行类定义的主体时,spam实际上是该主体范围内的局部变量;仅在执行元类(在这种情况下为type)时,才从这些局部变量创建类属性。[1]

对于老式的类,它还没有完全定义会发生什么,因此您几乎必须转向实现。特别是,没有任何步骤可以使用类定义的本地变量执行元类以生成类对象。但在大多数情况下,它的工作原理与实际情况差不多。



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

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



因此,如果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不为类定义生成单元格?

除非Guido记得并发现有趣的事足以撰写有关此的Python历史博客文章,否则我不确定我们是否会知道答案。 (当然,您可以尝试问他-在他的博客上发表评论或将电子邮件发送给任何看起来最相关的邮件列表都是最好的方法。)

但是,这是我的猜测:

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

类没有 __code__成员(或2.6之前的版本, co_code)。为什么?因为类定义一被定义就立即执行,而不再执行,那么为什么要麻烦呢?这意味着没有地方可以存储单元格,也没有可供引用的单元格。最重要的是,执行框架总是在执行完成后就消失,因为它没有任何外部引用。

当然,您可以更改此设置:将 __code__成员添加到类中,在其中创建单元格,然后,如果有人关闭了这些单元格,则执行后就可以像使用函数一样保持框架活动。那是个好主意吗?我不知道。我的猜测是,没有人问何时首次定义Python类的问题。虽然现在显而易见,类定义和函数定义有多少共同点,但我认为这是Guido的时光机的一个实例-他做出了一项设计决策,但没有意识到它将解决直到十年后才出现的问题。



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