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

Posted

技术标签:

【中文标题】Python 元类中的继承如何工作?【英文标题】:How does inheritance work in Python metaclass? 【发布时间】:2021-04-08 20:32:46 【问题描述】:

假设,我有一个自定义元类和一个与之关联的类:

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

据我了解,在class A语句的最后,会执行以下步骤:

    致电Meta('A', (), )。 因为第 1 步是一个内置调用,这意味着将调用type.__call__(...)。这是因为type 链接到Meta.__class__type.__call__(...) 依次运行另外两种方法(__new____init__)。 如果Meta 定义了这些方法中的一个或两个,那么在type.__call__ 内部,这些方法将被调用为Meta.__new__(...) 和/或Meta.__init__(...)。 类A 已创建并链接到Meta (A.__class__)。

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

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

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

    调用type('B', (), ) 而不是Meta,因为B.__class__type。 调用type.__call__(...),后者又运行另外两个方法(__new____init__)。 type.__new__(type, 'B', (A,), ). type.__init__(cls, 'B', (A,), ).

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

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

另外,如果我手动创建一个以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'>

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

【问题讨论】:

【参考方案1】:

所以 --- 一个可以回答的有点令人困惑的问题,并且有些简化 只需在交互模式下运行一些示例即可。

但要开始,当您声明时:

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

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

当我们创建新类时,比如在解析类语句class A: 时,type.__call__ 会被调用。但是这个调用是在 Meta 本身的 class 中搜索的。即“Meta”的“元类”——默认为type

请耐心等待: 当我们谈论一个没有自定义元类的普通类 E 时,您通过执行 E() 创建一个实例 - Python 在 E 是一个实例的类中搜索 __call__ 方法:即它的元类.因为它是类型,所以 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>


所以,简而言之:在创建一个作为Meta实例的类A时,调用Meta类的__call__方法。这将在元类 Meta 中调用 __init____new__。如果没有定义,普通的属性查找会在 Meta 的超类中调用这些方法,而 是“类型”。

现在,继续您的问题:当继承自具有自定义元类的类时,例如您的 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

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

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

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


如果您想以编程方式创建一个具有自定义“元元类”的类(上帝禁止任何人在学习目的之外需要它),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>

总结一下,如果一个类的超类有不同的元类,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

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


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

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

In [210]:

【讨论】:

【参考方案2】:

调用 type('B', (), ) 而不是 Meta,因为 B.class 是 type。

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

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

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

如果X类继承Y类,则X的元类与Y的元类相同。您可以在data model documentation找到详细信息。

来自文档:

类定义的适当元类被确定为 如下:

如果没有给出基类和显式元类,则使用 type();

如果给出了一个明确的元类并且它不是 type() 的实例, 然后直接作为元类使用;

如果 type() 的实例作为显式元类或基类给出 被定义,然后使用最派生的元类。

最派生的元类是从明确指定的 元类(如果有的话)和所有的元类(即类型(cls)) 指定的基类。派生最多的元类是一个 所有这些候选元类的子类型。如果没有 候选元类满足该标准,然后类定义 将因 TypeError 而失败。

【讨论】:

以上是关于Python 元类中的继承如何工作?的主要内容,如果未能解决你的问题,请参考以下文章

实现特殊的元类。继承类中的非化字段

元类中 __new__ 的行为(也在继承的上下文中)

在继承元类的类中调用 super 会引发错误

接口类

c++继承是如何工作的?

面向对象的深刻理解