为什么“type.\uuuu new\uuuuuuu”调用`\uuuuu init\u子类`?

2024-09-24 12:32:48 发布

您现在位置:Python中文网/ 问答频道 /正文

我注意到我不能以我想要的方式将__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__之前)运行的代码

还是我完全错过了什么

仅供参考,我找到了一些相关主题,但不完全是我想要的:

也许问这个问题更简洁的方式是“为什么Python(至少v3.9版)在Meta.__new__调用Parent.__init_subclass__而不是在__new__完成后立即调用它?”

请注意,在询问之后,我确实发现python.org围绕这个主题进行了一些讨论,但我认为他们没有阐明原因:


Tags: orgnewinitargscallmetakwargsclass
1条回答
网友
1楼 · 发布于 2024-09-24 12:32:48

狡猾

所以,它是这样的,因为它是用语言制造的。在为类创建过程添加特性时,人们不允许自定义When__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__方法,然后再调用它们。这样做的好处是可以按原样使用hyerarchy中现有的__init_subclass__(只要类本身是用Python编写的,而不是本机代码编写的:在这种情况下,临时删除该方法将不起作用)。它有一个严重的缺点,你必须自己扫描mro(你必须重新进行线性化),搜索所有现有的__init_subclass__,然后恢复它们-可能有一些难以发现的情况

相关问题 更多 >