gpt4 book ai didi

python - 为什么 `type.__new__` 调用 `__init_subclass__` ?

转载 作者:行者123 更新时间:2023-12-05 04:52:29 25 4
gpt4 key购买 nike

我注意到我不能以我想要的方式将 __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__,而不是有MetaMeta.__call____new__ 完成后立即调用它?

请注意,在询问之后,我确实找到了一些关于这个主题的 python.org 讨论,但我认为他们没有阐明原因:

最佳答案

棘手。

所以,它是这样的,因为它是用语言制作的。在为类创建过程添加功能时,人们不允许自定义 __init_subclass__ 或描述符 __set_name__ 或计算最终线性化 (mro):即所有这些都在 type.__new__ 中同时完成 - 任何编写的元类都必须在某个时候调用 type.__new__

但是,有一个可能的解决方法:如果 type.__new__ 不会“看到”__init_subclass__ 方法,则不会调用它。

因此,子元类有可能隐藏__init_subclass__,调用父类__new__,然后在离开它自己的__new__之前> 恢复并调用 __init_subclass__

所以,如果问题特别是你需要 __init_subclass__ 在 Django 的元类 __new__ 完全完成后运行,我可以想到两个选项,它们都涉及继承自Django 的 ORM 元类,对其进行修改,并将其用作模型的元类。

那么第一个选项就是在你的项目中使用不同于 __init_subclass__ 的另一个方法名。您的自定义元类调用 super().__new__() ,Django 和 Python 做他们的事情,您调用您的 __init_subclass2__ (或您选择的任何名称)。我认为这是最易于维护和最直接的方法。

第二个选项是我之前提到的:您的__new__ 方法检查所有基础是否出现__init_subclass__,然后暂时从它们所在的类中删除,存储原来的方法,调用super().__new__(),然后恢复__init_subclass__方法,之后再调用。这具有在层次结构中使用现有 __init_subclass__ 的优势(只要类本身是用 Python 而不是 native 代码编写的:在这种情况下,暂时删除该方法将不起作用) .它有一个严重的缺点,你必须自己扫描 mro(你将不得不重新进行线性化)搜索所有现有的 __init_subclass__ 并在之后恢复它们 - 可能有一些难以发现的极端情况.

关于python - 为什么 `type.__new__` 调用 `__init_subclass__` ?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/66480847/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com