4

查看Python 3.5中类型的文档super,它指出super(…)super(__class__, «first argument to function»). 令我惊讶的是,我编写了一个返回的方法__class__——它确实有效:

>>> class c:
...   def meth(self): return __class__
... 
>>> c().meth()
<class '__main__.c'>

显然,__class__是一个由函数闭包分配的自由变量:

>>> c.meth.__code__.co_freevars
('__class__',)
>>> c.meth.__closure__
(<cell at 0x7f6346a91048: type object at 0x55823b17f3a8>,)

我想知道在什么情况下自由变量与闭包相关联。我知道,如果我在创建类的过程中将函数分配给变量,则不会发生。

>>> def meth2(self): return __class__
... 
>>> meth2.__code__.co_freevars
()

即使我创建了一个新类并作为该创建的一部分为 分配了一些属性meth2meth2也不会以某种方式神奇地获得一个被填充的自由变量。

这并不奇怪,因为其中一部分似乎取决于编译器在编译代码时的词法状态。

我想确认__class__被视为自由变量的必要条件很简单:

  • 代码块中的引用__class__;和
  • def包含引用的__class__词法在class声明块中。

我还想了解正确填写该变量所需的条件是什么。似乎——至少从 Python 3.6 文档来看——以type.__new__(…)某种方式涉及到类似的东西。我无法确定如何type发挥作用,以及这一切如何与最终不调用type.__new__(…).

我特别困惑,因为当时我不认为命名空间的__setattr__方法用于将包含方法的属性分配给方法函数(因为它存在于最终构造的类对象上)。我知道这个命名空间对象的存在是因为它要么是通过使用class语句隐式构造的,要么是由元类的__prepare__方法显式构造的——但据我所知,元类构造了在函数对象设置为__class__ 之后填充的类对象类命名空间中的值。

4

1 回答 1

2

Python 的数据模型的文档中第 3.3.3.6 节- “创建类对象” - 你会发现以下内容:

[The] 类对象是由 . 的零参数形式引用的对象super()__class__是编译器创建的隐式闭包引用,如果类主体中的任何方法引用__class__super。这允许零参数形式super()正确识别基于词法作用域定义的类,而用于进行当前调用的类或实例是基于传递给方法的第一个参数来识别的。

……重点是我的。这证实了发生闭包的两个假定标准__class__:方法 def 中的“<code>__class__”引用,它本身在class语句中定义。

但是,“创建类对象”中的下一个¶继续说:

CPython 实现细节:在 CPython 3.6 及更高版本中,单元格作为类命名空间中的条目__class__传递给元类。__classcell__如果存在,则必须将其传播到 type.__new__调用才能正确初始化类。不这样做将导致RuntimeError在 Python 3.8 中出现。

……重点是他们的。这意味着,如果您正在使用带有__new__方法的元类——为了规定创建如此指定的类的术语——例如,例如:

class Meta(type):

    def __new__(metacls, name, bases, attributes, **kwargs):
        # Or whatever:
        if '__slots__' not in attributes:
            attributes['__slots__'] = tuple()

        # Call up, creating and returning the new class:
        return super().__new__(metacls, name,
                                        bases,
                                        attributes,
                                      **kwargs)

…最后一个super(…).__new__(…)电话实际上是在打电话type.__new__(…)。在现实生活中,如果您的元类继承自其他元类(例如,例如),可能会有一些其他祖传的“<code>__new__(...)”方法在此处和此处之间被调用abc.ABCMeta。但实际上,在您的Meta.__new__(…)方法内部,在方法入口点、super(…).__new__(…)调用和return-ing 新类对象之间,您可以通过†检查或设置最终__class__单元格变量的值。attributes['__classcell__']

现在至于这是否有用:我不知道。我用编程已经十年了;我完全使用元类‡,就像,绝对一直(无论好坏);在此过程中,我从未做过以下任何事情:

  1. 重新分配一个__class__属性;
  2. 检查__class__任何东西的单元格变量;也不
  3. __classcell__像任何容量一样,弄乱了这个假定的名称空间条目

……当然,你的编程经验会和我的不一样,谁知道是做什么的。并不是说上述任何一种策略都必然存在事实上的问题。但是我对将 Python 的类型系统和元编程工具弯曲到我的心血来潮并不陌生,而且这些特殊的东西从来没有表现出特别有用,尤其是当你在元类的一般上下文中工作时,以及它们的作用。

我想我的意思是,tl;博士:你正处于弄清楚元类的基础知识以及它们能做什么的风口浪尖——继续努力并进行实验,但一定要深入研究这个话题。的确!


† – 在阅读此类代码示例时,您经常会发现我的代码片段在此处将attributes字典称为namespaceorns或类似内容。都是一样的东西。

‡ – …以及 ABC、mixins 和类装饰器,__init_subclass__(…)以及滥用__mro_entries__(…)以谋取私利;等等,令人作呕

于 2020-03-31T13:41:32.980 回答