我注意到我不能以我想要的方式将__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围绕这个主题进行了一些讨论,但我认为他们没有阐明原因:
狡猾
所以,它是这样的,因为它是用语言制造的。在为类创建过程添加特性时,人们不允许自定义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__
,然后恢复它们-可能有一些难以发现的情况相关问题 更多 >
编程相关推荐