metaclass 的“__init_subclass__”方法在这个 metaclass 构造的 class 中不起作用
The metaclass's "__init_subclass__" method doesn't work in the class constructed by this metaclass
我的问题受到了这个的启发。
问题出在 3 级 class 模型 - 终止 classes(第 3 级)仅应存储在注册表中,但第 2 级正在干扰和也有存储,因为它们是第 1 级的子class。
我想通过使用 metaclass 摆脱第一级 class。通过这种方式,只剩下 2 class 级别 - 每组设置及其子项的基础 classes - 各种设置 classes,继承自相应的基础 class . metaclass 作为一个 class 工厂 - 它应该使用需要的方法创建基础 classes 并且不应该显示在继承树中。
但是我的想法行不通,因为好像__init_subclass__
方法(link方法)没有从metaclass复制到构造classes。与 __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'>]
我想为上述模型的 subclassing 过程添加功能,所以我重新定义了 type
的内置函数 __init_subclass__
,如下所示:
代码片段 № 2.
class Meta_Parent(type):
def __init_subclass__(cls, **kwargs):
super().__init_subclass__(**kwargs)
print(cls)
从我的角度来看,现在每个新的 class,由 Meta_Parent metaclass 构造(例如,Parent_One) 应该有 __init_subclass__
方法,因此,当每个 class 继承自这个新的 class 时,应该打印 subclass 名称],但它什么也不打印。也就是说,我的 __init_subclass__
方法在发生继承时不会被调用。
如果 Meta_Parent metaclass 是直接继承的,则有效:
代码片段 № 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 方法只属于 metaclass,不会传递给新构造的 classes,但是,我尝试 __init__
方法,它像我一开始所期望的那样工作 - 看起来 link 到 __init__
已经复制到每个 metaclass 的 class.
代码片段 № 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__
无效?
- 是否可以使用metaclass来实现我的想法?
1。为什么 __init__
有效,但 __init_subclass__
无效?
我通过GDB调试CPython找到了答案。
新 class(类型)的创建从 type_call() 函数开始。它主要做两件事:创建新类型对象和初始化此对象。
obj = type->tp_new(type, args, kwds);
是对象创建。它使用传递的参数调用类型的 tp_new
槽。默认情况下,tp_new
存储对 basic type
object's tp_new
slot 的引用,但如果任何祖先 class 实现了 __new__
方法,则引用将更改为 slot_tp_new
调度程序函数。然后 type->tp_new(type, args, kwds);
调用 slot_tp_new
函数,它自己调用 mro 链中的 __new__
方法的搜索。 tp_init
.
也是如此
subclass 初始化发生在新类型创建的末尾 - init_subclass(type, kwds). It searches the __init_subclass__
method in the mro chain of the just created new object by using the super object。在我的例子中,对象的 mro 链有两个项目:
print(Parent_One.__mro__)
### Output
(<class '__main__.Parent_One'>, <class 'object'>).
int res = type->tp_init(obj, args, kwds);
是对象初始化。它还在mro链中搜索__init__
方法,但使用metaclass mro,而不是刚刚创建的新对象的mro。在我的例子中,metaclass mro 有三个项目:
print(Meta_Parent.__mro__)
###Output
(<class '__main__.Meta_Parent'>, <class 'type'>, <class 'object'>)
简化执行图:
所以,答案是: __init_subclass__
和 __init__
方法在不同的地方搜索:
- 首先在
Parent_One
的__dict__
中搜索__init_subclass__
,然后在object
的__dict__
中搜索。
__init__
的搜索顺序是:Meta_Parent
的__dict__
、type
的__dict__
、object
的__dict__
.
2。有没有可能通过metaclass?
实现我的想法
我想出了以下解决方案。它有缺点 - __init__
方法被每个子 class 调用,包括子项,这意味着 - 所有子 class 都有 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'>}
我想出的解决方案use/like是:
class Meta_Parent(type):
def _init_subclass_override(cls, **kwargs):
super().__init_subclass__(**kwargs)
# Do whatever... I raise an exception if something is wrong
#
# i.e
# if sub-class's name does not start with "Child_"
# raise NameError
#
# cls is the actual class, Child_A in this case
class Parent_One(metaclass=Meta_Parent):
@classmethod
def __init_subclass__(cls, **kwargs):
Meta_Parent._init_subclass_override(cls, **kwargs)
### Parent_One's childs
class Child_A(Parent_One):
pass
我喜欢这个,因为它干掉了子class的创作code/checks。同时,如果你看到Parent_One
,你就知道每当创建子class时就会发生一些事情。
我在模仿我自己的接口功能(而不是使用 ABC)时这样做了,override
方法检查子 classes 中是否存在某些方法。
可以争论覆盖方法是否真的属于元class或其他地方。
我的问题受到了这个
问题出在 3 级 class 模型 - 终止 classes(第 3 级)仅应存储在注册表中,但第 2 级正在干扰和也有存储,因为它们是第 1 级的子class。
我想通过使用 metaclass 摆脱第一级 class。通过这种方式,只剩下 2 class 级别 - 每组设置及其子项的基础 classes - 各种设置 classes,继承自相应的基础 class . metaclass 作为一个 class 工厂 - 它应该使用需要的方法创建基础 classes 并且不应该显示在继承树中。
但是我的想法行不通,因为好像__init_subclass__
方法(link方法)没有从metaclass复制到构造classes。与 __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'>]
我想为上述模型的 subclassing 过程添加功能,所以我重新定义了 type
的内置函数 __init_subclass__
,如下所示:
代码片段 № 2.
class Meta_Parent(type):
def __init_subclass__(cls, **kwargs):
super().__init_subclass__(**kwargs)
print(cls)
从我的角度来看,现在每个新的 class,由 Meta_Parent metaclass 构造(例如,Parent_One) 应该有 __init_subclass__
方法,因此,当每个 class 继承自这个新的 class 时,应该打印 subclass 名称],但它什么也不打印。也就是说,我的 __init_subclass__
方法在发生继承时不会被调用。
如果 Meta_Parent metaclass 是直接继承的,则有效:
代码片段 № 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 方法只属于 metaclass,不会传递给新构造的 classes,但是,我尝试 __init__
方法,它像我一开始所期望的那样工作 - 看起来 link 到 __init__
已经复制到每个 metaclass 的 class.
代码片段 № 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__
无效? - 是否可以使用metaclass来实现我的想法?
1。为什么 __init__
有效,但 __init_subclass__
无效?
我通过GDB调试CPython找到了答案。
新 class(类型)的创建从 type_call() 函数开始。它主要做两件事:创建新类型对象和初始化此对象。
也是如此obj = type->tp_new(type, args, kwds);
是对象创建。它使用传递的参数调用类型的tp_new
槽。默认情况下,tp_new
存储对 basictype
object'stp_new
slot 的引用,但如果任何祖先 class 实现了__new__
方法,则引用将更改为slot_tp_new
调度程序函数。然后type->tp_new(type, args, kwds);
调用slot_tp_new
函数,它自己调用 mro 链中的__new__
方法的搜索。tp_init
.subclass 初始化发生在新类型创建的末尾 - init_subclass(type, kwds). It searches the
__init_subclass__
method in the mro chain of the just created new object by using the super object。在我的例子中,对象的 mro 链有两个项目:print(Parent_One.__mro__) ### Output (<class '__main__.Parent_One'>, <class 'object'>).
int res = type->tp_init(obj, args, kwds);
是对象初始化。它还在mro链中搜索__init__
方法,但使用metaclass mro,而不是刚刚创建的新对象的mro。在我的例子中,metaclass mro 有三个项目:print(Meta_Parent.__mro__) ###Output (<class '__main__.Meta_Parent'>, <class 'type'>, <class 'object'>)
简化执行图:
所以,答案是: __init_subclass__
和 __init__
方法在不同的地方搜索:
- 首先在
Parent_One
的__dict__
中搜索__init_subclass__
,然后在object
的__dict__
中搜索。 __init__
的搜索顺序是:Meta_Parent
的__dict__
、type
的__dict__
、object
的__dict__
.
2。有没有可能通过metaclass?
实现我的想法我想出了以下解决方案。它有缺点 - __init__
方法被每个子 class 调用,包括子项,这意味着 - 所有子 class 都有 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'>}
我想出的解决方案use/like是:
class Meta_Parent(type):
def _init_subclass_override(cls, **kwargs):
super().__init_subclass__(**kwargs)
# Do whatever... I raise an exception if something is wrong
#
# i.e
# if sub-class's name does not start with "Child_"
# raise NameError
#
# cls is the actual class, Child_A in this case
class Parent_One(metaclass=Meta_Parent):
@classmethod
def __init_subclass__(cls, **kwargs):
Meta_Parent._init_subclass_override(cls, **kwargs)
### Parent_One's childs
class Child_A(Parent_One):
pass
我喜欢这个,因为它干掉了子class的创作code/checks。同时,如果你看到Parent_One
,你就知道每当创建子class时就会发生一些事情。
我在模仿我自己的接口功能(而不是使用 ABC)时这样做了,override
方法检查子 classes 中是否存在某些方法。
可以争论覆盖方法是否真的属于元class或其他地方。