python-3.x - 元类的“__init_subclass__”方法在此元类构造的类中不起作用
问题描述
我的问题受到了这个问题的启发。
问题在于 3 级类模型 - 终止类(第 3 级)只应存储在注册表中,但第 2 级干扰并且也已存储,因为它们是第 1 级的子类。
我想通过使用元类来摆脱一级类。通过这种方式,剩下的只有 2 个类级别 - 每组设置及其子级的基类 - 从相应的基类继承的各种设置类。元类充当类工厂——它应该创建具有所需方法的基类,并且不应该显示在继承树中。
但是我的想法不起作用,因为似乎__init_subclass__
方法(方法的链接)没有从元类复制到构造类。与方法相反__init__
,这符合我的预期。
代码片段№1.模型的基本框架:
class Meta_Parent(type):
pass
class Parent_One(metaclass=Meta_Parent):
pass
class Child_A(Parent_One):
pass
class Child_B(Parent_One):
pass
class Child_C(Parent_One):
pass
print(Parent_One.__subclasses__())
输出:
[<class '__main__.Child_A'>, <class '__main__.Child_B'>, <class '__main__.Child_C'>]
我想为上述模型的子类化过程添加功能,所以我重新定义了type
's 内置函数,__init_subclass__
如下所示:
代码片段№ 2。
class Meta_Parent(type):
def __init_subclass__(cls, **kwargs):
super().__init_subclass__(**kwargs)
print(cls)
从我的角度来看,现在每个由Meta_Parent元类(例如Parent_One)构造的新类都应该有__init_subclass__
方法,因此,当每个类都从这个新类继承时,应该打印子类名称,但它什么也不打印。也就是说,__init_subclass__
当继承发生时,我的方法不会被调用。
如果Meta_Parent元类是直接继承的,它会起作用:
代码片段№ 3。
class Meta_Parent(type):
def __init_subclass__(cls, **kwargs):
super().__init_subclass__(**kwargs)
print(cls)
class Child_A(Meta_Parent):
pass
class Child_B(Meta_Parent):
pass
class Child_C(Meta_Parent):
pass
输出:
<class '__main__.Child_A'>
<class '__main__.Child_B'>
<class '__main__.Child_C'>
这里没什么奇怪的,__init_subclass__
正是为此目的而创建的。
我当时在想,dunder 方法仅属于元类,不会传递给新构造的类,但是随后,我尝试了该__init__
方法,它按我一开始所期望的那样工作 - 看起来链接__init__
已复制到每个元类的类。
代码片段№ 4。
class Meta_Parent(type):
def __init__(cls, name, base, dct):
super().__init__(name, base, dct)
print(cls)
输出:
<class '__main__.Parent_One'>
<class '__main__.Child_A'>
<class '__main__.Child_B'>
<class '__main__.Child_C'>
问题:
- 为什么
__init__
有效,但__init_subclass__
无效? - 是否可以通过使用元类来实现我的想法?
解决方案
1. 为什么__init__
有效,但__init_subclass__
无效?
我通过GDB调试CPython找到了答案。
新类(类型)的创建从type_call()函数开始。它做了两件主要的事情:一个新类型对象的创建和这个对象的初始化。
obj = type->tp_new(type, args, kwds);
是一个对象创建。tp_new
它使用传递的参数调用类型的槽。默认情况下,tp_new
stores 引用基本type
对象的tp_new
slot,但如果任何祖先类实现了该__new__
方法,则该引用将更改为slot_tp_new
调度程序函数。然后type->tp_new(type, args, kwds);
调用slot_tp_new
函数,然后它自己调用mro链中的__new__
方法搜索。同样的情况也发生在.tp_init
子类初始化发生在新类型创建结束时 - init_subclass(type, kwds)。它使用超级对象搜索刚刚创建的新对象
__init_subclass__
的mro链中的方法。在我的例子中,对象的 mro 链有两个项目:print(Parent_One.__mro__) ### Output (<class '__main__.Parent_One'>, <class 'object'>).
int res = type->tp_init(obj, args, kwds);
是一个对象初始化。它还搜索__init__
mro 链中的方法,但使用元类 mro,而不是刚刚创建的新对象的 mro。就我而言,元类 mro 具有三个项目:print(Meta_Parent.__mro__) ###Output (<class '__main__.Meta_Parent'>, <class 'type'>, <class 'object'>)
所以,答案是: __init_subclass__
在不同的__init__
地方搜索方法:
__init_subclass__
首先在Parent_One
's中搜索__dict__
,然后在object
's 中搜索__dict__
。- 按
__init__
以下顺序搜索:Meta_Parent
's__dict__
、type
's__dict__
、object
's__dict__
。
2. 是否可以通过使用元类来实现我的想法?
我想出了以下解决方案。它有缺点 -__init__
每个子类都调用该方法,包括子类,这意味着 - 所有子类都有属性,这是不必要的registry
。__init_subclass__
但它可以按照我在问题中的要求工作。
#!/usr/bin/python3
class Meta_Parent(type):
def __init__(cls, name, base, dct, **kwargs):
super().__init__(name, base, dct)
# Add the registry attribute to the each new child class.
# It is not needed in the terminal children though.
cls.registry = {}
@classmethod
def __init_subclass__(cls, setting=None, **kwargs):
super().__init_subclass__(**kwargs)
cls.registry[setting] = cls
# Assign the nested classmethod to the "__init_subclass__" attribute
# of each child class.
# It isn't needed in the terminal children too.
# May be there is a way to avoid adding these needless attributes
# (registry, __init_subclass__) to there. I don't think about it yet.
cls.__init_subclass__ = __init_subclass__
# Create two base classes.
# All child subclasses will be inherited from them.
class Parent_One(metaclass=Meta_Parent):
pass
class Parent_Two(metaclass=Meta_Parent):
pass
### Parent_One's childs
class Child_A(Parent_One, setting='Child_A'):
pass
class Child_B(Parent_One, setting='Child_B'):
pass
class Child_C(Parent_One, setting='Child_C'):
pass
### Parent_Two's childs
class Child_E(Parent_Two, setting='Child_E'):
pass
class Child_D(Parent_Two, setting='Child_D'):
pass
# Print results.
print("Parent_One.registry: ", Parent_One.registry)
print("#" * 100, "\n")
print("Parent_Two.registry: ", Parent_Two.registry)
输出
Parent_One.registry: {'Child_A': <class '__main__.Child_A'>, 'Child_B': <class '__main__.Child_B'>, 'Child_C': <class '__main__.Child_C'>}
####################################################################################################
Parent_Two.registry: {'Child_E': <class '__main__.Child_E'>, 'Child_D': <class '__main__.Child_D'>}
推荐阅读
- recursion - 在 ASM 和 LC3 中使用堆栈进行递归编程
- php - 将字符串从 ISO-8859-1 转换为 UTF8
- php - 按钮重定向到 php 文件
- ios - RxDataSources itemDeselected 没有被调用
- c# - 如何使用 SqlDataReader 返回和使用 IAsyncEnumerable
- javascript - 如何仅在按下键后运行我的功能?
- node-red - JSONata Node-RED 节点。在哪里可以找到
- flask - 使用烧瓶炼金术 API 发布方法的问题不起作用
- javascript - 如何比较数组中的每个元素并将具有相同数据的元素分组(在我的情况下为日期)?JavaScript
- python - 将复杂列表转换为数据框表,第一个值作为第二列标题