元类的“ __init_subclass__”方法在此元类构造的类中不起作用

时间:2019-08-26 15:29:55

标签: python-3.x metaclass

我的问题是受此question启发的。

存在3级类模型的问题-终止类(3级)仅应存储在注册表中,但是2级具有干扰性并且也已存储,因为它们是1级的子类。一级。

我想通过使用metaclass摆脱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的内置__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'>

问题:

  1. 为什么__init__有效,但是__init_subclass__无效?
  2. 是否可以通过使用元类来实现我的想法?

2 个答案:

答案 0 :(得分:1)

我想出并使用/喜欢的解决方案是:

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

我之所以这样,是因为它使子类创建代码/检查程序干燥。同时,如果看到Parent_One,则说明创建子类时会发生某些事情。

我是在模拟自己的接口功能(而不是使用ABC)的同时这么做的,override方法检查子类中某些方法的存在。

可以争论重写方法是真正属于元类还是其他地方。

答案 1 :(得分:0)

1。为什么__init__有效,但__init_subclass__无效?

我通过调试 GDB CPython 找到了答案。

  1. 新类(类型)的创建从type_call()函数开始。它主要做两件事:创建新类型的对象和初始化该对象。
  2. obj = type->tp_new(type, args, kwds);是对象的创建。它使用传递的参数来调用类型的tp_new插槽。默认情况下,tp_new存储对basic type object's tp_new slot的引用,但是如果任何祖先类实现了__new__方法,则该引用将更改为slot_tp_new调度程序函数。然后type->tp_new(type, args, kwds);调用slot_tp_new函数,它依次调用 mro 链中的__new__方法搜索。 tp_init也会发生同样的情况。
  3. 子类初始化发生在新类型创建-init_subclass(type, kwds)的结尾。它使用super object刚创建的新对象的mro链中搜索__init_subclass__方法。在我的情况下,对象的mro链有两个项目:

    print(Parent_One.__mro__)
    ### Output
    (<class '__main__.Parent_One'>, <class 'object'>).
    
  4. int res = type->tp_init(obj, args, kwds);是对象初始化。它还在mro链中搜索__init__方法,,但使用元类mro,而不是刚创建的新对象的mro 。在我的情况下,元类mro具有三个项目:

    print(Meta_Parent.__mro__)
    ###Output
    (<class '__main__.Meta_Parent'>, <class 'type'>, <class 'object'>)
    

简化的执行图: enter image description here

因此,答案是::在不同位置搜索__init_subclass____init__方法:

  • 首先在__init_subclass__的{​​{1}}中搜索Parent_One,然后在__dict__的{​​{1}}中搜索。
  • 按以下顺序搜索object__dict__的{​​{1}},__init__的{​​{1}},Meta_Parent的{​​{ 1}}。

2。是否可以通过使用元类来实现我的想法?

我想出了以下解决方案。它有缺点-__dict__方法由每个子类(包括子类)调用,这意味着-所有子类都具有type__dict__属性,这是不必要的。但这按我在问题中的要求进行。

object

输出

__dict__