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

Hen*_*hia 4 python inheritance metaclass python-3.x python-3.9

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

class Meta(type): pass
class A(metaclass=Meta): pass
Run Code Online (Sandbox Code Playgroud)

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

  1. 称呼Meta('A', (), {})
  2. 因为步骤 1 是内置调用,这意味着type.__call__(...)将被调用。这是因为type链接到了Meta.__class__.
  3. type.__call__(...)依次运行另外两个方法( a__new__和 a __init__)。
  4. 如果Meta定义了这两个方法中的一个或两个,那么这些方法内部type.__call__将被调用为Meta.__new__(...)and/or Meta.__init__(...)
  5. A已创建并链接到Meta( A.__class__)。

现在,假设我有一个子类A

class Meta(type): pass
class A(metaclass=Meta): pass
class B(A): pass
Run Code Online (Sandbox Code Playgroud)

在陈述的最后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元类创建的。但打印出来的B.__class__不是.Metatype

print(B.__class__) #<class '__main__.Meta'>
Run Code Online (Sandbox Code Playgroud)

另外,如果我手动创建一个A作为父类的类,则创建的类将再次链接到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'>
Run Code Online (Sandbox Code Playgroud)

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

jsb*_*eno 6

因此,这是一个可以回答的有点令人困惑的问题,并且通过简单地在交互模式下运行一些示例来简化一些问题。

但首先,当你说:

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

这是所发生事情的简化。

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

请耐心听我说:当我们谈论一个没有自定义元类的普通类 E 时,您通过执行以下操作创建一个实例E()- Python 搜索属于实例__call__的类中的方法E:即它的元类。既然是类型,那么就type.__call__被调用。正如您所说,它type.__call__调用__new____init__方法,但不仅适用于元类:它在任何对象实例化中编排这些调用 - Python 中的任何对象实例化中都使用完全相同的机制:普通对象和类:



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>


Run Code Online (Sandbox Code Playgroud)

所以,简而言之:当创建类A(它是Meta的实例)时,__call__调用Meta类的方法。这将调用元类 Meta 中的__init__and 。__new__如果没有定义这些方法,普通属性查找将调用 Meta 超类中的这些方法,Meta 恰好也是 type”。

现在,继续讨论你的问题:当一个类继承自一个具有自定义元类的类时,比如你的B类,Python 将其超类中派生最多的元类作为它自己的元类,而不是type. 无需显式声明自定义元类。实际上,这就是需要元类而不仅仅是类装饰器的原因:它们仅影响声明它们的类,并且对其他子类没有影响。


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
Run Code Online (Sandbox Code Playgroud)

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

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

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


Run Code Online (Sandbox Code Playgroud)

如果您想以编程方式创建一个具有自定义“元元类”的类(上帝禁止除了学习目的之外的任何其他用途),模块中有一个特殊的调用可以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>
Run Code Online (Sandbox Code Playgroud)

最后,请注意,如果一个类的超类具有不同的元类,Python 将根本拒绝创建一个类。这在“现实世界”代码中有些常见,当人们尝试使用 ORM 的某个框架中的基类创建抽象类(使用自定义元类)时,该框架通常也有一个自定义元类:


                                                                                                                                         
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

Run Code Online (Sandbox Code Playgroud)

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


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

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

In [210]:
Run Code Online (Sandbox Code Playgroud)