为什么`type.__new__`会调用`__init_subclass__`?

3
我注意到我不能以我想要的方式使用__init_subclass__与Django模型类。似乎在父类的__init_subclass__方法运行时,元类还没有完成创建子类。虽然我理解问题所在,并可以通过制作自定义元类来规避它,但我不理解的是为什么!在我的脑海中,我倾向于认为像__new__这样的任何调用都应在像__init__这样的任何调用之前完成。但这对于元类和__init_subclass__并非如此,正如这里所示:
class MetaMeta(type):
    print('parsing MetaMeta')
    def __call__(cls, *args, **kwargs):
        print('entering MetaMeta.__call__')
        instance = super().__call__(*args, **kwargs)
        print('leaving MetaMeta.__call__')
        return instance

class Meta(type, metaclass=MetaMeta):
    print('parsing Meta')
    def __init__(self, *args, **kwargs):
        print('  entering Meta.__init__')
        super().__init__(*args, **kwargs)
        print('  leaving Meta.__init__')
    def __new__(cls, *args, **kwargs):
        print(f'  entering Meta.__new__')
        instance = super().__new__(cls, *args, **kwargs)
        print('  leaving Meta.__new__')
        return instance

class Parent(object, metaclass=Meta):
    print('parsing Parent')
    def __init_subclass__(cls, *args, **kwargs):
        print('    entering Parent.__init_subclass__')
        super().__init_subclass__(*args, **kwargs)
        print('    leaving Parent.__init_subclass__')

class Child(Parent):
    print('parsing Child')

这导致:
parsing MetaMeta
parsing Meta
parsing Parent
entering MetaMeta.__call__
  entering Meta.__new__
  leaving Meta.__new__
  entering Meta.__init__
  leaving Meta.__init__
leaving MetaMeta.__call__
parsing Child
entering MetaMeta.__call__
  entering Meta.__new__
    entering Parent.__init_subclass__
    leaving Parent.__init_subclass__
  leaving Meta.__new__
  entering Meta.__init__
  leaving Meta.__init__
leaving MetaMeta.__call__

一个元类在调用__init_subclass__后仍然可以在Meta.__new__中设置类。这对我来说似乎很奇怪。为什么会这样,有没有办法在不使用自定义元类的情况下在Parent中提供代码,使其在Meta.__new__之后(可能在Meta.__init__之前)完全运行?
或者我完全错了?
FYI,我找到了一些相关主题,但并不完全是我要找的: 也许更简洁的问法是,为什么Python(至少是v3.9)会让Meta.__new__调用Parent.__init_subclass__,而不是让MetaMeta.__call__在完成__new__后立即调用它?请注意,在提出这个问题后,我找到了一些关于这个主题的python.org讨论,但我认为它们没有澄清原因。
1个回答

2
棘手。
因此,这是这样的,因为它在语言中被制作成这样。当为类创建过程添加功能时,人们没有允许定制何时计算__init_subclass__、描述符__set_name__或最终的线性化(mro):所有这些都是在type.__new__内一次完成的,任何编写的元类都必须在某个时候调用type.__new__
然而,有一种可能的解决方法:如果type.__new__看不到一个__init_subclass__方法,它就不会被调用。
因此,子元类可以将__init_subclass__藏起来,在调用父级__new__之后,在自己的__new__之前恢复并调用__init_subclass__
因此,如果问题特别指出需要在Django的元类__new__完全完成后运行__init_subclass__,我想到了两个选项,两者都涉及从Django的ORM元类继承,修改它,并将其用作您的模型的元类。
然后第一种选择只是在项目中使用除__init_subclass__之外的另一个方法名称。您的自定义元类调用super().__new__(),Django和Python会做它们的事情,然后您调用您的__init_subclass2__(或其他您选择的名称)。我认为这是最可维护和直接的方法。
第二个选择就是我之前提到的:您的__new__方法检查所有基类是否有任何__init_subclass__出现,从它们所在的类中删除,暂时存储原始方法,调用super().__new__(),然后恢复__init_subclass__方法,并在以后调用它们。它的优点是与层次结构中现有的__init_subclass__一起工作(只要类本身是用Python编写而不是本地代码:在这种情况下,暂时删除方法将无效)。它的严重缺点是你必须自己扫描mro(你必须重新做线性化),搜索所有现有的__init_subclass__并在恢复它们之后进行恢复-可能有一些难以发现的角落情况。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接