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

2024-04-20 04:16:17 发布

您现在位置:Python中文网/ 问答频道 /正文

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

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

据我所知,在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__(...)和/或Meta.__init__(...)调用
  5. A被创建并链接到MetaA.__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如何创建class B/C以及B/C如何链接到Meta


Tags: 方法newinit链接maintype步骤pass
2条回答

所以,这是一个有点令人困惑的问题,可以回答,有些则简化了 只需在交互模式下运行一些示例

但首先,当您声明:

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>


因此,简而言之:当创建一个类a(Meta的实例)时,调用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__的“metameta”类的调用硬编码,并且忽略了“元类的自定义元类”:

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

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


如果您想通过编程创建一个具有自定义“meta-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>

最后,请注意,如果一个类的超类有不同的元类,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]:

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?

如果X类继承了Y类,那么X的元类与Y的元类相同。您可以找到有关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.

相关问题 更多 >