python中__init_subclass__与多个类. 为什么__init_subclass没有被调用两次?
我在找关于如何在多个类中使用 __init_subclass__ 的信息时,发现了一个错误报告:https://bugs.python.org/issue42674
提交这个报告的人说 __init_subclass__ 只被调用了一次,而不是两次。
这个问题被关闭了,理由是这并不是一个错误,评论是:
测试脚本中的两个子类没有调用 super()。当它们调用 super() 时,两个 init_subclasses 都会被调用。
报告中附带了以下代码:
class ClassOne:
@classmethod
def __init_subclass__(cls):
print(f"ClassOne.__init_subclass__( cls = {cls} )")
class ClassTwo:
@classmethod
def __init_subclass__(cls):
print(f"ClassTwo.__init_subclass__( cls = {cls} )")
class MyClass(ClassOne, ClassTwo):
def __init__(self):
super().__init__()
super(ClassOne, self).__init__()
我搞不清楚怎么才能触发两个 __init_subclass__ 方法。这是我的尝试:
class ClassOne:
@classmethod
def __init_subclass__(cls):
print(f"ClassOne.__init_subclass__( cls = {cls} )")
def __init__(self):
super().__init__()
print(f"ClassOne.__init__( self = {self} )")
class ClassTwo:
@classmethod
def __init_subclass__(cls):
print(f"ClassTwo.__init_subclass__( cls = {cls} )")
def __init__(self):
super().__init__()
print(f"ClassTwo.__init__( self = {self} )")
class MyClass(ClassOne, ClassTwo):
def __init__(self):
super(ClassOne, self).__init__()
super(ClassTwo, self).__init__()
还有结果:
> python3 init_subclass.py
ClassOne.__init_subclass__( cls = <class '__main__.MyClass'> )
我在运行 Python 3.7。
2 个回答
这段话的意思是,在每个 __init_subclass__
的类方法里,都需要调用 super().__init_subclass__
。这个要求可以在 PEP-0487 的相关部分找到。
class ClassOne:
@classmethod
def __init_subclass__(cls, **kwargs):
super().__init_subclass__(**kwargs)
print(f"ClassOne.__init_subclass__( cls = {cls} )")
class ClassTwo:
@classmethod
def __init_subclass__(cls, **kwargs):
super().__init_subclass__(**kwargs)
print(f"ClassTwo.__init_subclass__( cls = {cls} )")
class MyClass(ClassOne, ClassTwo):
...
这样做之后,应该会导致两个 __init_subclass__
都被调用。
ClassTwo.__init_subclass__( cls = <class '__main__.MyClass'> )
ClassOne.__init_subclass__( cls = <class '__main__.MyClass'> )
当一个类是通过元类 type
创建的时候,它的 __init_subclass__
类方法会在它的 super()
对象上被调用,这一点可以在它的 Python 等效实现的 实现细节 中看到:
class NewType(type):
def __new__(cls, *args, **kwargs):
if len(args) != 3:
return super().__new__(cls, *args)
name, bases, ns = args
init = ns.get('__init_subclass__')
if isinstance(init, types.FunctionType):
ns['__init_subclass__'] = classmethod(init)
self = super().__new__(cls, name, bases, ns)
for k, v in self.__dict__.items():
func = getattr(v, '__set_name__', None)
if func is not None:
func(self, k)
super(self, self).__init_subclass__(**kwargs)
return self
所以,属性名 __init_subclass__
遵循通常的 方法解析顺序(MRO) 规则,也就是在第一个定义这个属性的基类中进行解析。在这个 bug 报告的测试案例中,这个基类是 ClassOne
,而且因为 ClassOne.__init_subclass__
没有调用 super().__init_subclass__
,所以控制权没有传递给下一个基类 ClassTwo
,这就是为什么只会输出 ClassOne.__init_subclass__
的结果。
要解决这个问题,可以让每个 __init_subclass__
方法都合作调用 super().__init_subclass__
,这样所有基类的 __init_subclass__
方法都会被调用:
class ClassOne:
@classmethod
def __init_subclass__(cls):
super().__init_subclass__()
print(f"ClassOne.__init_subclass__( cls = {cls} )")
class ClassTwo:
@classmethod
def __init_subclass__(cls):
super().__init_subclass__()
print(f"ClassTwo.__init_subclass__( cls = {cls} )")