python中__init_subclass__与多个类. 为什么__init_subclass没有被调用两次?

1 投票
2 回答
42 浏览
提问于 2025-04-14 17:48

我在找关于如何在多个类中使用 __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 个回答

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'> )
2

当一个类是通过元类 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} )")

撰写回答