如何解决Python 元类中的继承是如何工作的?
假设我有一个自定义元类和一个链接到它的类:
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.__class__
不应该给出 type
而不是 Meta
?我的理由是 B
是使用默认 type
元类创建的。但是打印出 B.__class__
给出的是 Meta
而不是 type
。
print(B.__class__) #<class '__main__.Meta'>
此外,如果我手动创建一个以 A
作为父类的类,创建的类也会链接到 Meta
。
C = type.__call__(type,'C',{})
print(C.__class__) #<class '__main__.Meta'>
#or
D = type.__new__(type,'D',{})
print(D.__class__) #<class '__main__.Meta'>
我的问题是 Python 如何创建 class B/C
以及 B/C
如何链接到 Meta
?
解决方法
调用 type('B',(),{}) 而不是 Meta,因为 B.class 是类型。
正如你后面提到的,它不是。
>>> 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 而失败。
,所以 --- 一个有点令人困惑的问题,可以回答,有些简化 只需在交互模式下运行一些示例即可。
但首先,当您声明时:
type.__call__(...) in turn run two other methods (a __new__ and a __init__).
这是对发生的事情的简化。
当我们创建新类时,就像在解析类语句 class A:
时一样,type.__call__
会被调用。但是这个调用是在 Meta
本身的 class 中搜索的。也就是说,“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,**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 的超类中调用这些方法,而这恰好也是“类型”。
现在,继续您的问题:当从具有自定义元类的类(例如您的 B
类)继承时,Python 将其超类中派生最多的元类作为自己的元类,而不是 {{1} }.无需显式声明自定义元类。实际上,这就是需要元类而不仅仅是类装饰器的原因:它们只影响声明它们的类,而对进一步的子类没有影响。
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
语句中,派生类的元类也将是超类的元类。但是请注意,在这种情况下,我们将对“metameta”类的调用硬编码为 class
并且“元类的自定义元类”被忽略:
type.__new__
如果您想以编程方式创建一个具有自定义“元元类”的类(上帝禁止除了学习目的以外的任何其他用途),
In [187]: C = type("C",(A,),{})
In [188]: C()
At the metaclass __call__
Out[188]: <__main__.C at 0x7fa653cb0d60>
模块中有一个特殊调用可以执行此操作:
types
总结一下,如果一个类的超类有不同的元类,Python 将完全拒绝创建一个类。这在“现实世界”代码中有些常见,当人们尝试在一些带有 ORM 的框架中创建带有基类的抽象类(使用自定义元类)时,通常也有一个自定义元类:
In [192]: import types
In [193]: D = types.new_class("D",{})
Now at the meta-meta class
In [194]: D()
At the metaclass __call__
Out[194]: <__main__.D at 0x7fa6682959a0>
可以通过生成继承自的派生元类来修复
两个祖先分支中的元类(这要求两个元类
表现良好,使用
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
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。