Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

python multiple inheritance from different paths with same method name

With the following code sample, can super be used, or C has to call A.foo and B.foo explicitly?

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) 
like image 397
sayap Avatar asked Sep 28 '10 07:09

sayap


People also ask

What is the problem with multiple inheritance in Python?

The Problem with Multiple Inheritance If you allow multiple inheritance then you have to face the fact that you might inherit the same class more than once. In Python as all classes inherit from object, potentially multiple copies of object are inherited whenever multiple inheritance is used.

How do you achieve multiple inheritance in Python?

Inheritance is the mechanism to achieve the re-usability of code as one class(child class) can derive the properties of another class(parent class). It also provides transitivity ie. if class C inherits from P then all the sub-classes of C would also inherit from P.

Can two classes inherit from each other Python?

In Python a class can inherit from more than one class. If a class inherits, it has the methods and variables from the parent classes. In essence, it's called multiple inheritance because a class can inherit from multiple classes. This is a concept from object orientated programming.


2 Answers

super is indeed intended for this situation, but it only works if you use it consistently. If the base classes don't also all use super it won't work, and unless the method is in object you have to use something like a common base class to terminate the chain of super calls.

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 asks why there has to be a common base:

Without FooBase that implements foo but doesn't call super() the last class that does call super() will get an attribute error as there is no base method to call.

If there were separate base classes class A(AFooBase): and class B(BFooBase): the super() call in A would call the method in AFooBase and the method in B would never be called. When the base is common to all of the classes it goes to the end of the method resolution order and you can be certain that no matter how the classes are defined the base class method will be the last one called.

like image 92
Duncan Avatar answered Sep 22 '22 23:09

Duncan


Thanks for all those contributed to this thread.

To summarize:

  • The (currently) accepted answer is inaccurate. The correct description should be: super() is NOT ONLY good for resolving single inheritance, BUT ALSO multiple inheritance. And the reason is well explained in @blckknght 's comment:

    While explicitly calling the base class methods can work for very simple scenarios like the questioner's example, it will break down if the base classes themselves inherit from a common base and you don't want the ultimate base class's method to be called twice. This is known as "diamond inheritance" and it is a big problem for many multiple inheritance systems (like in C++). Python's collaborative multiple inheritance (with super()) lets you solve easily it in many cases (though that's not to say a cooperative multiple inheritance hierarchy is easy to design or always a good idea).

  • The proper way, as @duncan pointed out, is to use super(), but use it consistently.

    super is indeed intended for this situation, but it only works if you use it consistently. If the base classes don't also all use super it won't work, and unless the method is in object you have to use something like a common base class to terminate the chain of super calls.

    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 

    But it is also worth to point out that, the method calling order may NOT seem intuitive at the first thought. The result is:

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

    This seemingly strange order The actual calling order is still C, A, B, which is based on MRO. In other words,

    super() will call the foo method on the "first" "next" super class. This is based on the Method Resolution Order (__mro__) for the class C.

    -- Quoted and modified from @Manoj-Govindan 's answer

    >>> C.__mro__ (<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <type 'object'>) >>>  
  • As a rule of thumb, if you want to travel back to ALL parent methods but do not really care the invoke order, use super() consisently. Otherwise, you may choose to explicitly call parent methods in a specific order.

  • Do not mix the usage of super() and explicit calling though. Otherwise you will end up nasty duplication like mentioned in this answer.

UPDATE: If you want to dive deeper...

In short, using super(...) consistently in the whole class family will ensure ALL same-name methods from ancestors being called once, in the order of MRO. Such call-ALL-ancestors (rather than call-only-the-first-candidate) behavior may be conceptually easier to accept, if the method happens to be a __init__(), see example in this blog post.

Saying "follow the MRO order" might not be very precise, though. It actually always follows the "grandchild" 's MRO, somehow. It is fascinating to see it in action. The result of following program may not be exactly what you thought it would be. Pay attention to how A.__mro__ remains same in 2 different calling stack, yet how super(A, self).name or super(A, self).foo() behave DIFFERENTLY when triggered by A().foo() and by C().foo(). See the result quoted at the end.

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 

And its result from Python 2.7.12 is:

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 
like image 30
RayLuo Avatar answered Sep 26 '22 23:09

RayLuo