Python 元类中的继承是如何工作的?

How does inheritance work in Python metaclass?

假设,我有一个自定义元class和一个链接到它的class:

class Meta(type): pass
class A(metaclass=Meta): pass

据我了解,在class A语句的末尾,执行了以下步骤:

  1. 呼叫Meta('A', (), {}).
  2. 因为第1步是内置调用,也就是说type.__call__(...)会被调用。这是因为 type 链接到 Meta.__class__.
  3. type.__call__(...)依次运行另外两个方法(一个__new__和一个__init__)。
  4. 如果 Meta 定义了其中一个或两个方法,那么在 type.__call__ 内部,这些方法将被调用为 Meta.__new__(...) and/or Meta.__init__(...).
  5. class A 已创建并链接到 Meta (A.__class__).

现在,假设我有 A 的子class:

class Meta(type): pass
class A(metaclass=Meta): pass
class B(A): pass

class B语句的末尾,以下步骤是否正确?

  1. 调用 type('B', (), {}) 而不是 Meta,因为 B.__class__type
  2. 调用 type.__call__(...) 进而 运行 其他两种方法(__new____init__)。
  3. type.__new__(type, 'B', (A,), {}).
  4. type.__init__(cls, 'B', (A,), {}).

假设上述步骤是正确的(我对此表示怀疑),难道 B.__class__ 不应该给出 type 而不是 Meta 吗?我的推理是 B 是使用默认 type metaclass 创建的。但是打印出 B.__class__ 给出 Meta 而不是 type.

print(B.__class__) #<class '__main__.Meta'>

此外,如果我手动创建一个 class 并将 A 作为父级,创建的 class 将再次链接到 Meta.

C = type.__call__(type, 'C', (A,), {})
print(C.__class__) #<class '__main__.Meta'>

#or

D = type.__new__(type, 'D', (A,), {})
print(D.__class__) #<class '__main__.Meta'>

我的问题是 Python 如何创建 class B/C 以及 B/C 如何链接到 Meta?

Call type('B', (), {}) instead of Meta, because B.class is type.

正如您稍后指出的那样,它不是。

>>> class Meta(type): pass
...
>>> class A(metaclass=Meta): pass
...
>>> class B(A): pass
...
>>> type(B)
<class '__main__.Meta'>
>>>

My question is how Python create the class B/C and how B/C is linked to the Meta?

如果Xclass继承了Yclass,则X的metaclass与meta[=相同Y 的 25=]。您可以在 data model documentation.

上找到详细信息

来自文档:

The appropriate metaclass for a class definition is determined as follows:

if no bases and no explicit metaclass are given, then type() is used;

if an explicit metaclass is given and it is not an instance of type(), then it is used directly as the metaclass;

if an instance of type() is given as the explicit metaclass, or bases are defined, then the most derived metaclass is used.

The most derived metaclass is selected from the explicitly specified metaclass (if any) and the metaclasses (i.e. type(cls)) of all specified base classes. The most derived metaclass is one which is a subtype of all of these candidate metaclasses. If none of the candidate metaclasses meets that criterion, then the class definition will fail with TypeError.

所以 --- 一个可以回答的有点令人困惑的问题,并且简化了一些 通过简单地 运行 交互模式中的一些示例。

但首先,当您声明:

type.__call__(...) in turn run two other methods (a __new__ and a __init__).

这是对发生的事情的简化。

当我们创建新的 class 时,就像解析 class 语句 class A: 一样,type.__call__ 会被调用。但是这个调用是在Meta本身的class中搜索的。也就是说,“Meta”的“metaclass”——默认情况下是 type.

请耐心等待: 当我们谈论没有自定义元 class 的普通 class E 时,您通过执行 E() - Python 搜索 __call__ 方法来创建实例在 class 其中 E 是一个实例:即它的 metaclass。因为它是类型,所以 type.__call__ 被调用。正如您所说,它是 type.__call__ 调用 __new____init__ 方法,但不仅限于 metaclasses:它在任何对象实例化中编排这些调用 - 确切Python 中的任何对象实例化都使用相同的机制:普通对象和 classes:



In [178]: class MetaMeta(type): 
     ...:     def __call__(metacls, *args, **kw): 
     ...:         print("Now at the meta-meta class") 
     ...:         return super().__call__(*args, **kw) 
     ...:                         

In [179]: class EmptyMeta(type, metaclass=MetaMeta): 
     ...:     def __call__(cls, *args, **kw): 
     ...:         print("At the metaclass __call__") 
     ...:         return super().__call__(*args, **kw) 
     ...:          
     ...:      
     ...:                         

In [180]: class A(metaclass=EmptyMeta): 
     ...:     pass 
     ...:                         
Now at the meta-meta class

In [181]: a = A()                 
At the metaclass __call__

In [182]: class Direct(metaclass=MetaMeta): pass                     

In [183]: Direct()                
Now at the meta-meta class
Out[183]: <__main__.Direct at 0x7fa66bc72c10>


所以,简而言之:当创建一个classA,它是Meta的一个实例时,会调用Meta的class的__call__方法。这将在 metaclass Meta 中调用 __init____new__。如果那些没有定义,普通的属性查找会在Meta的superclass中调用这些方法,这恰好also是"type"。

现在,继续您的问题:当一个人从 class 继承自具有自定义元 class 时,例如您的 B class、Python 将其超class 中最派生的元class 作为自己的元class,而不是type。无需显式声明自定义元class。实际上,这就是需要 metaclass 而不仅仅是 Class 装饰器的原因:它们只影响声明它们的 class ,并且对进一步的 sub[=70 没有影响=]es.


In [184]: class B(A): pass        
Now at the meta-meta class

In [185]: B()                     
At the metaclass __call__
Out[185]: <__main__.B at 0x7fa6682ab3a0>

In [186]: B.__class__             
Out[186]: __main__.EmptyMeta

即使显式调用 type 而不是 class 语句,派生的 class' metaclass 也将是 metaclass超级class。但是请注意,在这种情况下,我们将对“metameta”class 的调用硬编码为 type.__new__,并且忽略了“metaclass 的自定义元 class” :

                               
In [187]: C = type("C", (A, ), {})

In [188]: C()                     
At the metaclass __call__
Out[188]: <__main__.C at 0x7fa653cb0d60>


如果您想以编程方式创建一个具有自定义“元元 class”的 class(除了学习目的,上帝禁止任何人需要它),在types 执行此操作的模块:


In [192]: import types            

In [193]: D = types.new_class("D", (A,), {})                         
Now at the meta-meta class

In [194]: D()                     
At the metaclass __call__
Out[194]: <__main__.D at 0x7fa6682959a0>

总结一下,请注意,如果 class 的超 class 有不同的元 class,Python 将拒绝创建 class 完全没有。这在“现实世界”代码中有些常见,当人们尝试在带有 ORM 的某些框架中创建带有基础 class 的抽象 classes(使用自定义元 class)时,这通常还有自定义元class:


                                                                                                                                         
In [203]: class Meta1(type): pass 

In [204]: class Meta2(type): pass 

In [205]: class A(metaclass=Meta1): pass                             

In [206]: class B(metaclass=Meta2): pass                             

In [207]: class C(A, B): pass     
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-207-1def53cc27f4> in <module>
----> 1 class C(A, B): pass

TypeError: metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases

可以通过生成继承自的派生元 class 来修复 两个祖先分支中的 metaclasses(这需要两个 metaclasses 表现良好,使用 super() 而不是对 type 的硬编码调用 - 但是 维护良好且流行的框架就是这种情况):


In [208]: class Meta3(Meta1, Meta2): pass                            

In [209]: class C(A, B, metaclass=Meta3): pass                       

In [210]: