【问题标题】:The metaclass's "__init_subclass__" method doesn't work in the class constructed by this metaclass元类的“__init_subclass__”方法在此元类构造的类中不起作用
【发布时间】:2019-12-30 19:40:14
【问题描述】:

我的问题受到了question 的启发。

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

我想通过使用元类来摆脱一级类。通过这种方式,剩下的只有 2 个类级别 - 每组设置及其子级的基类 - 从相应的基类继承的各种设置类。元类充当类工厂 - 它应该创建具有所需方法的基类,并且不应该显示在继承树中。

但我的想法不起作用,因为似乎__init_subclass__ 方法(方法的链接)没有从元类复制到构造类。与__init__ 方法相比,它按我的预期工作。

代码sn-p№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__,如下所示:

代码 sn-p № 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 元类是直接继承的,它会起作用:

代码 sn-p № 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__ 的链接已复制到每个元类的类。

代码 sn-p № 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. 是否可以通过使用元类来实现我的想法?

【问题讨论】:

    标签: python-3.x metaclass


    【解决方案1】:

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

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

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

    2. obj = type-&gt;tp_new(type, args, kwds); 是一个对象创建。它使用传递的参数调用类型的tp_new 槽。默认情况下,tp_new 存储对basic type object's tp_new slot 的引用,但如果任何祖先类实现了__new__ 方法,则引用将更改为slot_tp_new 调度程序函数。然后type-&gt;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-&gt;tp_init(obj, args, kwds); 是一个对象初始化。它还搜索 mro 链中的 __init__ 方法,但使用元类 mro,而不是刚刚创建的新对象的 mro。在我的例子中,元类 mro 有三个项目:

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

    简化的执行图:

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

    • __init_subclass__首先在Parent_One__dict__中搜索,然后在object__dict__中搜索。
    • __init__ 的搜索顺序如下:Meta_Parent__dict__type__dict__object__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'>}
    

    【讨论】:

      【解决方案2】:

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

      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 方法检查子类中是否存在某些方法。

      人们可以争论重写方法是否真的属于元类,或者其他地方。

      【讨论】:

        猜你喜欢
        • 2013-06-18
        • 2020-04-15
        • 2014-09-25
        • 1970-01-01
        • 2020-03-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多