__class__ 单元格值是如何在 class 方法中设置的?

How is the __class__ cell value set in class methods?

查看 the documentation of the super type in Python 3.5,它注意到 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>,)

我想知道在什么情况下自由变量与闭包关联。我知道如果我将一个函数分配给一个变量作为创建 class 的一部分,它不会发生。

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

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

这不足为奇,因为部分原因似乎取决于编译代码时编译器的词法状态。

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

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

的元 classes 相互作用

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

the docs for Python’s data model, § 3.3.3.6 – “创建 class 对象” – 您会发现以下内容:

[The] class object is the one that will be referenced by the zero-argument form of super(). __class__ is an implicit closure reference created by the compiler if any methods in a class body refer to either __class__ or super. This allows the zero argument form of super() to correctly identify the class being defined based on lexical scoping, while the class or instance that was used to make the current call is identified based on the first argument passed to the method.

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

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

CPython implementation detail: In CPython 3.6 and later, the __class__ cell is passed to the metaclass as a __classcell__ entry in the class namespace. If present, this must be propagated up to the type.__new__ call in order for the class to be initialized correctly. Failing to do so will result in a RuntimeError in Python 3.8.

…重点是他们的。这意味着如果您使用带有 __new__ 方法的 metaclass – 为了规定创建如此指定的 classes 的条款 – 例如:

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__(…)。在现实生活中,如果您的 metaclass 继承自其他 metaclasses(例如 abc.ABCMeta).但是,实际上,在您的 Meta.__new__(…) 方法内部,在方法入口点、super(…).__new__(…) 调用和 return-ing 新的 class 对象之间,您可以检查或设置通过 attributes['__classcell__']†.

的最终 __class__ 单元格变量的值

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

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

... 自然,你的编程体验会和我的不一样,谁知道呢。并不是说上述任何一种策略都 事实上 必然存在问题。但是我对 Python 的类型系统和元编程设施随心所欲并不陌生,这些特殊的东西从来没有表现出特别有用,尤其是当你在 meta[=79= 的一般上下文中工作时]es,以及他们的工作。

我想我的意思是,tl;dr:你正处于弄清楚 metaclasses 的基础知识以及它们可以做什么的风口浪尖——继续努力并进行实验,但要调查话题既有深度又有气息。确实!


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

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