为啥`type.__new__`调用`__init_subclass__`?
Posted
技术标签:
【中文标题】为啥`type.__new__`调用`__init_subclass__`?【英文标题】:Why does `type.__new__` call `__init_subclass__`?为什么`type.__new__`调用`__init_subclass__`? 【发布时间】:2021-06-03 10:38:48 【问题描述】:我注意到我无法以我想要的方式将__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__
之前)运行的代码?
或者我完全错过了什么?
仅供参考,我找到了一些相关的主题,但不是我想要的:
The call order of python3 metaclass Arguments of __new__ and __init__ for metaclasses https://docs.python.org/3/reference/datamodel.html#customizing-class-creation也许问这个问题的更简洁的方法是“为什么 Python(至少是 v3.9)有Meta.__new__
调用Parent.__init_subclass__
,而不是让MetaMeta.__call__
在__new__
完成后立即调用它?
请注意,在询问之后,我确实找到了一些关于这个主题的 python.org 讨论,但我认为他们没有说明原因:
https://bugs.python.org/issue42775 https://mail.python.org/archives/list/python-dev@python.org/thread/ZMRRNSFSLJZDGGZ66CFCYQBINU62CDNX/【问题讨论】:
【参考方案1】:棘手。
所以,就是这样,因为它是用语言制作的。在为类创建过程添加功能时,人们不允许自定义何时 __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__
并在之后恢复它们 - 可能存在一些难以发现的极端情况。
【讨论】:
以上是关于为啥`type.__new__`调用`__init_subclass__`?的主要内容,如果未能解决你的问题,请参考以下文章
自定义元类 __call__,__init__,__new__总结
原创Python 对象创建过程中元类, __new__, __call__, __init__ 的处理