在Python中混合super和经典调用

9 投票
2 回答
1271 浏览
提问于 2025-04-16 04:16

首先,让我引用一下《专家Python编程》这本书中的一段话:

在下面的例子中,一个C类通过__init__方法调用它的父类,这样会导致B类被调用两次!

class A(object):
    def __init__(self):
        print "A"
        super(A, self).__init__()

class B(object):
    def __init__(self):
        print "B"
        super(B, self).__init__()

class C(A,B):
    def __init__(self):
        print "C"
        A.__init__(self)
        B.__init__(self)

print "MRO:", [x.__name__ for x in C.__mro__]  #prints MRO: ['C', 'A', 'B', 'object']
C()  #prints C A B B

最后,这里有个解释,告诉我们发生了什么:

这是因为调用了A.__init__(self),这个调用是通过C类的实例进行的,因此会导致super(A, self).__init__()调用B的构造函数。 换句话说,super应该在整个类的层级中使用。问题是,有时候这个层级的一部分是在第三方代码中。

我不明白为什么"super(A, self).__init__()会调用B的构造函数"。请解释一下这个过程。非常感谢。

2 个回答

10

要理解这个行为,你需要知道 super 不是直接调用基类,而是沿着 __mro__ 的顺序查找下一个匹配的方法。所以,调用 super(A, self).__init__() 时,它查看 __mro__ == ['C', 'A', 'B', 'object'],发现 B 是下一个有匹配方法的类,然后调用 B 的方法(构造函数)。

如果你把 C 改成

class C(A,B):
    def __init__(self):
        print "C1"
        A.__init__(self)
        print "C2"
        B.__init__(self)
        print "C3"

你会得到

MRO: ['C', 'A', 'B', 'object']
C1
A
B
C2
B
C3

这展示了 A 的构造函数是如何调用 B 的。

4

关于 super 的文档说明是这样的:

它返回一个代理对象,这个对象会把方法调用转发给父类或兄弟类。这在访问被重写的方法时非常有用。搜索的顺序和 getattr() 一样,只是类型本身会被跳过。

当你在 C 类中执行 A.__init__(self) 时,super(A, self) 会返回 <super: <class 'A'>, <C object>>。因为实例是 C<C object>),所以会找到 C 继承层次中的所有类,并对它们调用 __init__ 方法。因此,你会看到 'B' 被调用了两次。

为了验证这一点,可以再添加一个类 'Z',让 'C' 也继承自 'Z'。看看会发生什么。

class Z(object):
    def __init__(self):
        print "Z"
        super(Z, self).__init__()

class C(A, B, Z):    
    def __init__(self):
        print "C"
        A.__init__(self)
        B.__init__(self)
        Z.__init__(self)

在这种情况下,A 会调用 BZ。而 B 也会调用 Z

撰写回答