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

4
假设我有一个自定义元类和一个与之相关联的类:
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__(...) 和/或 Meta.__init__(...) 的形式调用这些方法。
  5. 创建类 A 并将其链接到 Meta (A.__class__)。
现在,假设我有一个 A 的子类:
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元类创建的。但是打印出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如何创建B/C类以及B/C类与Meta的关联方式是什么?

2个回答

6

所以——这是一个有点混淆的问题,可以通过在交互模式下运行一些示例来回答,并简化。

但首先,在您陈述时:

type.__call__(...)会依次运行两个其他方法(__new__和__init__)。

这是一个简化了的过程。

当我们创建新类,比如解析一个类语句 class A:,确实会调用 type.__call__。但是这个调用在 Meta 的“类”中搜索。也就是说,“Meta”的“元类”——默认为type

请耐心听我解释: 当我们谈论没有自定义元类的普通类 E,E() 创建实例时,Python 会在 E 所属的类——也就是它的元类中搜索 __call__ 方法。由于它是 type,因此会调用 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类的超类中的这些方法,其超类也是“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

即使在显式调用 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]:

3

使用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的元类相同。您可以在数据模型文档中找到详细信息。
来自文档:
一个类定义的适当元类如下确定: 如果没有给出基类和显式元类,则使用type(); 如果给出了显式元类且它不是type()的实例,则直接使用它作为元类; 如果给出了type()的实例作为显式元类,或者定义了基类,则使用最终派生的元类。 最终派生的元类是从明确指定的元类(如果有)和所有指定基类的元类(即type(cls))中选择的。最终派生的元类是这些候选元类的子类型。如果没有候选元类满足该标准,则类定义将失败并引发TypeError异常。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接