Python 多重继承来自不同路径且方法名相同

44 投票
9 回答
40191 浏览
提问于 2025-04-16 04:41

在下面这个代码示例中,super可以使用吗?还是说C必须明确地调用A.fooB.foo

class A(object):
    def foo(self):
        print("A.foo()")


class B(object):
    def foo(self):
        print("B.foo()")


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

9 个回答

31

感谢所有参与这个讨论的人。

总结一下:

  • 目前被接受的答案并不准确。 正确的描述应该是:super() 不仅适用于单继承,也适用于多继承。原因在于 @blckknght 的评论中有很好的解释:

    虽然在一些简单的情况下,明确调用基类的方法可以奏效,比如提问者的例子,但如果基类本身也继承自一个共同的基类,而你又不想让最顶层的基类的方法被调用两次,这种方法就会出问题。这种情况被称为“钻石继承”,在很多多继承系统中(比如 C++)是一个大问题。Python 的协作多继承(通过 super())可以在很多情况下轻松解决这个问题(不过这并不意味着设计一个协作的多继承结构很简单或总是个好主意)。

  • 正确的方法,正如 @duncan 指出的,是使用 super(),但要一致地使用它。

    super 确实是为这种情况设计的,但只有在你一致使用它的情况下才有效。如果基类也没有都使用 super,那么它就无法正常工作。而且,除非方法在 object 中,否则你需要使用像共同基类这样的东西来结束 super 调用的链。

    class FooBase(object):
        def foo(self): pass
    
    class A(FooBase):
        def foo(self):
            super(A, self).foo()
            print 'A.foo()'
    
    class B(FooBase):
        def foo(self):
            super(B, self).foo()
            print 'B.foo()'
    
    class C(A, B):
        def foo(self):
            super(C, self).foo()
            print 'C.foo()'
    
    C().foo()  # Run this
    

    不过值得指出的是,方法调用的顺序一开始可能看起来并不直观。结果是:

    B.foo()
    A.foo()
    C.foo()
    

    这个看似奇怪的顺序 实际的调用顺序仍然是 C, A, B,这是基于方法解析顺序(MRO)。换句话说,

    super() 将调用 “第一个” “下一个” 超类的 foo 方法。这是基于类 C 的方法解析顺序(__mro__)。

    -- 引用并修改自 @Manoj-Govindan 的回答

    >>> C.__mro__
    (<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <type 'object'>)
    >>> 
    
  • 作为一个经验法则,如果你想调用所有父类的方法,但不太在乎调用顺序,可以一致地使用 super()。否则,你可以选择按照特定顺序显式调用父类的方法。

  • 不过,不要混合使用 super() 和显式调用。否则你会遇到像 这个回答 中提到的麻烦重复调用问题。

更新:如果你想深入了解...

简而言之,在整个类族中一致地使用 super(...) 将确保所有同名的祖先方法只被调用一次,按照 MRO 的顺序。这样的调用所有祖先的方法(而不是 只调用第一个候选)的行为,可能更容易被接受,特别是当这个方法是 __init__() 时,可以参考这篇博客中的例子

不过,说“遵循 MRO 顺序”可能并不非常准确。实际上,它总是遵循“孙类”的 MRO,某种程度上是这样。看到它的实际效果非常有趣。下面的程序结果可能和你想象的并不完全一样。注意 A.__mro__ 在两个不同的调用栈中保持不变,但 super(A, self).namesuper(A, self).foo() 在通过 A().foo()C().foo() 触发时的行为是不同的。请查看最后引用的结果。

class FooBase(object):
    name = "FooBase"
    def foo(self):
        print('         Base.foo() begins')
        print("         My name is: %s" % self.name)
        print("         My super's name is not available")
        print('         Base.foo() ends')

class A(FooBase):
    name = "A"
    def foo(self):
        print('     A.foo() begins')
        print("     My name is: %s" % self.name)
        print("     My super's name is: %s" % super(A, self).name)
        print("     A.__mro__ is %s" % str(A.__mro__))
        super(A, self).foo()
        print('     A.foo() ends')

class B(FooBase):
    name = "B"
    def foo(self):
        print('     B.foo() begins')
        print("     My name is: %s" % self.name)
        print("     My super's name is: %s" % super(B, self).name)
        print("     B.__mro__ is %s" % str(B.__mro__))
        super(B, self).foo()
        print('     B.foo() ends')

class C(A, B):
    name = "C"
    def foo(self):
        print 'C.foo() begins'
        print("My name is: %s" % self.name)
        print("My super's name is: %s" % super(C, self).name)
        print(" C.__mro__ is %s" % str(C.__mro__))
        super(C, self).foo()
        print('C.foo() ends')


print("We will call A.foo()")
A().foo()

print("We will call C.foo()")
C().foo()  # Run this to see how each foo() is called ONLY ONCE

它在 Python 2.7.12 中的结果是:

We will call A.foo()
     A.foo() begins
     My name is: A
     My super's name is: FooBase
     A.__mro__ is (<class '__main__.A'>, <class '__main__.FooBase'>, <type 'object'>)
         Base.foo() begins
         My name is: A
         My super's name is not available
         Base.foo() ends
     A.foo() ends
We will call C.foo()
C.foo() begins
My name is: C
My super's name is: A
 C.__mro__ is (<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <class '__main__.FooBase'>, <type 'object'>)
     A.foo() begins
     My name is: C
     My super's name is: B
     A.__mro__ is (<class '__main__.A'>, <class '__main__.FooBase'>, <type 'object'>)
     B.foo() begins
     My name is: C
     My super's name is: FooBase
     B.__mro__ is (<class '__main__.B'>, <class '__main__.FooBase'>, <type 'object'>)
         Base.foo() begins
         My name is: C
         My super's name is not available
         Base.foo() ends
     B.foo() ends
     A.foo() ends
C.foo() ends
35

super 这个关键词确实是为这种情况设计的,但它只有在你一致使用的时候才有效。如果基础类(也就是父类)没有都使用 super,那么它就不会起作用。而且,除非这个方法是在 object 类里,否则你需要用一个共同的基类来结束 super 调用的链。

class FooBase(object):
    def foo(self): pass

class A(FooBase):
    def foo(self):
        super(A, self).foo()
        print 'A.foo()'

class B(FooBase):
    def foo(self):
        super(B, self).foo()
        print 'B.foo()'

class C(A, B):
    def foo(self):
        super(C, self).foo()
        print 'C.foo()'

@Marcin 问为什么必须有一个共同的基类:

如果没有 FooBase 这个类来实现 foo 方法,但它又不调用 super(),那么最后一个调用 super() 的类就会出现属性错误,因为没有基础方法可以调用。

如果有两个不同的基类,比如 class A(AFooBase):class B(BFooBase):,那么 A 中的 super() 调用会去调用 AFooBase 中的方法,而 B 中的方法就永远不会被调用。当所有类都有一个共同的基类时,它会在方法解析顺序的最后,这样你就可以确定无论这些类是怎么定义的,基类的方法都会是最后被调用的。

25

super() 这个东西在调用方法的时候,只会找到一个特定的类。所以如果你是从多个类继承的,想要同时调用这几个类里的方法,那你就得明确地去调用它们。比如说,你可以直接写 A.foo(self) 来调用类 A 里的方法。

撰写回答