【问题标题】:python multiple inheritance from different paths with same method namepython从具有相同方法名称的不同路径的多重继承
【发布时间】:2010-09-28 07:05:13
【问题描述】:

使用以下代码示例,可以使用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)

【问题讨论】:

    标签: python multiple-inheritance


    【解决方案1】:

    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 询问为什么必须有一个共同的基础:

    如果没有实现foo 但不调用super()FooBase,最后一个调用super() 的类将出现属性错误,因为没有可调用的基方法。

    如果有单独的基类class A(AFooBase):class B(BFooBase):A 中的super() 调用将调用AFooBase 中的方法,而B 中的方法将永远不会被调用。当基类对所有类都是通用的时,它会进入方法解析顺序的末尾,并且您可以确定无论类如何定义,基类方法都将是最后一个调用的方法。

    【讨论】:

    • 我认为这是一个比接受的答案更丰富的答案。您能否详细说明为什么需要公共基类来完成这项工作?
    • @marcin 你不需要一个共同的基础来使 super() 工作。 @duncan 只是在演示 super() 在处理钻石继承时如何更好。顺便说一句,调用C().foo() 的输出将是B.foo() A.foo(). C.foo() 而不是“A.. B.. C..”命令,原因是MRO 在Manoj's answer中解释
    • @RayLuo 这个答案字面意思是说需要一个共同的基础
    • @Marcin,我更新了我的答案。是不是更清楚了?
    • @Duncan Indeed.
    【解决方案2】:

    感谢所有为这个帖子做出贡献的人。

    总结一下:

    • The (currently) accepted answer 不准确。 正确的描述应该是:super() 不仅适用于解决单继承,还适用于多继承。原因在@blckknght 的评论中得到了很好的解释:

      虽然显式调用基类方法可以用于非常简单的场景,例如提问者的示例,但如果基类本身继承自一个公共基类并且您不希望最终基类的方法被调用两次,它就会崩溃.这被称为“钻石继承”,对于许多多重继承系统(如在 C++ 中)来说,这是一个大问题。 Python 的协作多重继承(使用 super())可以让您在许多情况下轻松解决它(尽管这并不是说协作多重继承层次结构易于设计或总是一个好主意)。

    • 正确的方法,如@duncan pointed out,是使用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() 将调用 "first" "next" 超类的 foo 方法。这基于类 C 的方法解析顺序 (__mro__)。

      --引用自@Manoj-Govindan 's answer修改

      >>> C.__mro__
      (<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <type 'object'>)
      >>> 
      
    • 根据经验,如果您想返回所有父方法但并不真正关心调用顺序,请始终使用super()。否则,您可以选择以特定顺序显式调用父方法。

    • 不要混合使用 super() 和显式调用。否则你会像this answer 中提到的那样导致讨厌的重复。

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

    简而言之,在整个类族中一致地使用super(...) 将确保按MRO 的顺序调用来自祖先的ALL 同名方法一次。如果方法恰好是 __init__()see example in this blog post,那么这种 call-ALL-ancestors(而不是 call-only-the-first-candidate)行为在概念上可能更容易接受。

    不过,说“遵循 MRO 订单”可能不是很准确。不知何故,它实际上总是遵循“孙子”的 MRO。看到它在行动中令人着迷。以下程序的结果可能与您想象的不完全一样。注意A.__mro__ 在 2 个不同的调用堆栈中如何保持相同,但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
    

    【讨论】:

    • 其实这里的方法调用顺序是C,A,B。它似乎只是BAC,因为每个方法首先链接到下一个,并且仅在另一个返回时打印自己的字符串。如果您将print 语句放在super 调用之前,那么顺序就不会那么令人惊讶了。或者在之前和之后放置打印。
    • 感谢@Duncan 指出。我使用它并相应地记录了更多的发现(如果不是更多的混乱)。哈哈
    【解决方案3】:

    super() 只会为给定方法解析单个类类型,因此如果您从多个类继承并希望在这两个类中调用该方法,则需要显式执行此操作。 A.foo(self)

    【讨论】:

    • 虽然显式调用基类方法可以适用于非常简单的场景,例如提问者的示例,但如果基类本身继承自一个公共基础并且您不这样做,它就会崩溃'不希望最终基类的方法被调用两次。这被称为“钻石继承”,对于许多多重继承系统(如在 C++ 中)来说,这是一个大问题。 Python 的协作多重继承(使用super())让您在许多情况下轻松解决它(尽管这并不是说协作多重继承层次结构易于设计或总是一个好主意)。
    • 请举例说明如何“明确地做”
    • @Jonathan A.foo(self) 是对 A 的 foo() 的显式调用
    【解决方案4】:

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

    >>> C.__mro__
    (<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <type 'object'>)
    >>> 
    

    因此,如果您调用super(C, self).foo(),则会调用A.foo。如果将继承顺序更改为class C(B, A):,则相反。 __mro__ 现在看起来像:

    >>> C.__mro__
    (<class '__main__.C'>, <class '__main__.B'>, <class '__main__.A'>, <type 'object'>)
    >>> 
    

    如果您在进行此更改后调用super(C, self).foo(),则将调用B.foo()

    【讨论】:

      【解决方案5】:

      如果您添加了以下内容:

      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):
              super(C, self).foo()
              print 'C.foo()'
              A.foo(self)
              B.foo(self)
      
      
      c = C()
      c.foo()
      

      那么 super(C, self).foo() 指的是 A.foo

      输出是

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

      [编辑:包括附加信息和链接]

      【讨论】:

      • 是的,那是我的问题。所以 super 不适合这种情况?
      • @sayap :我已经编辑了我的答案以包含与了解 super 如何工作相关的链接。它可以工作,但它会首先找到 A.foo,如果 A.foo 不在那里,那么 B.foo 就会被调查。
      • 我为什么要让 A.foo() 被调用两次?
      • @sayap:这只是一个例子。这表明 super 使用 MRO 来查找要调用的 foo。这可能是搜索 A.foo 然后 B.foo。如果您需要同时调用 A.foo 和 B.foo,请显式调用,否则您希望基类之一实现 foo,然后调用 super(..).foo。在这种情况下,您只希望其中一个实现 foo。
      【解决方案6】:

      正如上面邓肯所说,您可以获得可预测的结果,特别是如果您始终使用 super()。

      以下测试的结果对我有帮助:

      class FooBase(object):
          def foo(self):
              print 'FooBase.foo()'
      
      class A(FooBase):
          def foo(self):
              print 'A.foo() before super()'
              super(A, self).foo()
              print 'A.foo() after super()'
      
      class B(FooBase):
          def foo(self):
              print 'B.foo() before super()'
              super(B, self).foo()
              print 'B.foo() after super()'
      
      class C(A, B):
          def foo(self):
              print 'C.foo() before super()'
              super(C, self).foo()
              print 'C.foo() after super()'
      

      这将打印出来:

      >>> c = C()
      >>> c.foo()
      C.foo() before super()
      A.foo() before super()
      B.foo() before super()
      FooBase.foo()
      B.foo() after super()
      A.foo() after super()
      C.foo() after super()
      

      【讨论】:

        【解决方案7】:

        super 可以做到这一点

        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()'
                super(C, self).foo() # calls A.foo()
                super(A, self).foo() # calls B.foo()
        

        【讨论】:

        • 那么调用super(C, self).foo()super(A, self).foo() 而不是A.foo()B.foo() 有什么好处?
        【解决方案8】:
        class A:
            def article(self):
                print("Class A")
        
        class B:
            def article(self):
                print("Class B")
        
        class C(A,B):
            def article(self):
                print("Class C ::\n")
                super().article()
        
        x = C()
        x.article()
        

        输出:

        C 类 ::

        A类

        【讨论】:

          【解决方案9】:

          在这里你可以看到具有相同方法的类是如何被调用的:-

          这里,在 D(A,B,C) 类的方法 'feat()' 中

          super(D, self).feature2() ----> 会调用A类的方法(feature2)。

          super(A, self).feature2() ----> 会调用B类的方法(feature2)。

          super(B, self).feature2() ----> 会调用C类的方法(feature2)。

          class A:
          
              def __init__(self):
                  print("in A Init")
          
              def feature1(self):
                  print("Feature 1-A working")
          
              def feature2(self):
                  print("Feature 2-A working")
          
          class B:
          
              def __init__(self):
                  print("in B Init")
          
              def feature1(self):
                  print("Feature 1-B working")
          
              def feature2(self):
                  print("Feature 2-B working")
          
          class C:
          
              def __init__(self):
                  print("in C Init")
          
              def feature1(self):
                  print("Feature 1-C working")
          
              def feature2(self):
                  print("Feature 2-C working")
          
              
          
          class D(A,B,C):
          
              def __init__(self):
                  super().__init__()
                  print("in D init")
          
          
              def feat(self):
                  super(D, self).feature2()
                  super(A, self).feature2()
              
                  print('\n\n')
          
                  ### B().feature2() is calling explicitly, but 
                  ### super(A, self).feature2() is not.
                  
                  ### Both will give same result in below,
                  ### It will print True below
          
                  print(B().feature2() == super(A, self).feature2())
          
                  print('\n\n')
              
                  super(B, self).feature2()
                  print(D.__mro__)
          
          
          a1 = D()
          a1.feat()
          

          结果是:-

          in A Init
          in D init
          Feature 2-A working
          Feature 2-B working
          
          
          
          in B Init
          Feature 2-B working
          Feature 2-B working
          True
          
          
          
          Feature 2-C working
          (<class '__main__.D'>, <class '__main__.A'>, <class '__main__.B'>, <class 
          '__main__.C'>, <class 'object'>)
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2015-03-20
            • 2011-01-23
            • 2020-10-18
            • 1970-01-01
            • 1970-01-01
            • 2013-02-16
            • 2011-02-19
            相关资源
            最近更新 更多