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

Min*_*Max 4 metaclass python-3.x

我的问题受到了这个问题的启发。

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

我想通过使用元类摆脱 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__())
Run Code Online (Sandbox Code Playgroud)

输出:

[<class '__main__.Child_A'>, <class '__main__.Child_B'>, <class '__main__.Child_C'>]
Run Code Online (Sandbox Code Playgroud)

我想为上述模型的子类化过程添加功能,所以我重新定义了type's 的内置函数__init_subclass__

代码片段?2.

class Meta_Parent(type):
    def __init_subclass__(cls, **kwargs):
        super().__init_subclass__(**kwargs)
        print(cls)
Run Code Online (Sandbox Code Playgroud)

从我的角度来看,现在每个由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
Run Code Online (Sandbox Code Playgroud)

输出:

<class '__main__.Child_A'>
<class '__main__.Child_B'>
<class '__main__.Child_C'>
Run Code Online (Sandbox Code Playgroud)

这里没有什么奇怪的,__init_subclass__正是为此目的而创建的。

我当时在想,dunder 方法只属于元类,不会传递给新构造的类,但是随后,我尝试了该__init__方法,并且它按我一开始的预期工作 - 看起来链接__init__已复制到每个元类的类。

代码片段?4.

class Meta_Parent(type):
    def __init__(cls, name, base, dct):
        super().__init__(name, base, dct)
        print(cls)
Run Code Online (Sandbox Code Playgroud)

输出:

<class '__main__.Parent_One'>
<class '__main__.Child_A'>
<class '__main__.Child_B'>
<class '__main__.Child_C'>
Run Code Online (Sandbox Code Playgroud)

问题:

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

Min*_*Max 5

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

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

  1. 新类(类型)的创建从type_call()函数开始。它主要做两件事:一个新类型对象的创建和这个对象的初始化。

  2. obj = type->tp_new(type, args, kwds);是一个对象创建。它tp_new使用传递的参数调用类型的槽。默认情况下,tp_new存储对基本type对象的tp_newslot 的引用,但如果任何祖先类实现了该__new__方法,则该引用将更改为slot_tp_new调度程序函数。然后type->tp_new(type, args, kwds);调用slot_tp_new函数,它依次调用mro链中的搜索__new__方法。同样的情况发生在.tp_init

  3. 子类初始化发生在新类型创建结束时 - init_subclass(type, kwds)。它使用超级对象搜索刚刚创建的新对象__init_subclass__的 mro 链中的方法。在我的情况下,对象的 mro 链有两个项目:

    print(Parent_One.__mro__)
    ### Output
    (<class '__main__.Parent_One'>, <class 'object'>).
    
    Run Code Online (Sandbox Code Playgroud)
  4. 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'>)
    
    Run Code Online (Sandbox Code Playgroud)

简化的执行图: 在此处输入图片说明

所以,答案是: 在不同的地方搜索__init_subclass____init__方法:

  • __init_subclass__首先进行搜索在Parent_One__dict__,然后在object__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)
Run Code Online (Sandbox Code Playgroud)

输出

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'>}
Run Code Online (Sandbox Code Playgroud)