Python - metaclass 将变量范围从实例更改为 class

Python - metaclass changes variable scope from instance to class

运行 以下代码:

class NonMeta:
    def __new__(cls):
        x = super().__new__(cls)
        x.attr = 100
        return x


class Meta(type):
    def __new__(mcs, name, bases, dct):
        x = super().__new__(mcs, name, bases, dct)
        x.attr = 100
        return x


class WithMeta(metaclass=Meta):
    pass


print(WithMeta.attr)
print(NonMeta.attr)

结果

/usr/bin/python3.7 /home/lookash/PycharmProjects/PythonLearning/classes.py
100
Traceback (most recent call last):
  File "/home/lookash/PycharmProjects/PythonLearning/classes.py", line 20, in <module>
    print(NonMeta.attr)
AttributeError: type object 'NonMeta' has no attribute 'attr'

为什么 WithMeta class 中的 attr 是一个 class 变量,而在 NonMeta class 中它是一个实例变量?

NonMeta.__new__中,xNonMeta的一个实例,因此对x.attr的赋值是为实例x创建一个实例属性。

Meta.__new__里面,xMeta的一个实例,即一个新的class。在这种情况下,x 是 class WithMeta,因此对 x.attr 的赋值会创建一个 class 属性。

class 语句在某种意义上是调用元类型的语法糖。也就是说,

class WithMeta(metaclass=Meta):
    pass

等同于

WithMeta = Meta('WithMeta', (object,), {})
# == Meta.__new__(Meta, 'WithMeta', (object,), {})

metaclass 的实例是声明他们将使用它的 classes(及其子classes)。因此,当您在 Meta.__new__ 中执行 x.attr = 100 时,x 是一个 class(在本例中是 metaclass、WithMeta 的一个实例)。

这与 运行 在 NonMeta.__new__ 中的同一行不同,其中 xNonMeta 的一个实例,而 class 本身不是' 永远不会被分配 attr 属性。

出于这个原因,更改 __new__ 方法中的代码以使用与 x 不同且更好的名称可能是个好主意。在 NonMeta 中,一个好的名称可能是 self,因为这是方法中实例的传统名称。在 Meta 中,您可以使用 cls,因为实例将是 class.

没有 metaclass 的等效代码与你正在做的 with metaclass 很简单:

class NonMeta:
    x = 100

classes 中定义的 __new__ 方法仅在创建 class 的实例时调用 - 它不会 运行 这样做。在这段代码中:

class NonMeta:
    def __new__(cls):
        x = super().__new__(cls)
        x.attr = 100
        return x

来自 super().__new__(cls) 并存储在 x 中的 return 只是实例本身 - 以名称 self 传递给常规实例的同一对象方法(包括__init__)。

另一方面,

Metaclass 在创建 class 时具有其 __new__(和 __init__)方法 运行。在普通的模块导入中,这发生在 class 语句及其主体被执行时。在主体的末尾,Python 运行 时间具有创建新 class 所需的所有信息,并且元 class' __new__ 被调用.无论它 returns 变成 class。由于它的 return 值设置了 x 属性,它已经在 class.

上可见