10

I was looking into Python's super method and multiple inheritance. I read along something like when we use super to call a base method which has implementation in all base classes, only one class' method will be called even with variety of arguments. For example,

class Base1(object):
    def __init__(self, a):
        print "In Base 1"

class Base2(object):
    def __init__(self):
        print "In Base 2"

class Child(Base1, Base2):
    def __init__(self):
        super(Child, self).__init__('Intended for base 1')
        super(Child, self).__init__()# Intended for base 2

This produces TyepError for the first super method. super would call whichever method implementation it first recognizes and gives TypeError instead of checking for other classes down the road. However, this will be much more clear and work fine when we do the following:

class Child(Base1, Base2):
    def __init__(self):
        Base1.__init__(self, 'Intended for base 1')
        Base2.__init__(self) # Intended for base 2

This leads to two questions:

  1. Is __init__ method a static method or a class method?
  2. Why use super, which implicitly choose the method on it's own rather than explicit call to the method like the latter example? It looks lot more cleaner than using super to me. So what is the advantage of using super over the second way(other than writing the base class name with the method call)
thiruvenkadam
  • 4,170
  • 4
  • 27
  • 26
  • Its a class method (aka instance method if I understand you correctly). Static dont have self as the first argument. Super is in general better, as you dont need to known name of the base class. – Marcin Feb 18 '15 at 06:36
  • But if we are going to use the base class method, it would be preferable to know which class method we are calling, in terms of multiple inheritance right? – thiruvenkadam Feb 18 '15 at 06:39
  • There's no way the second code can work fine. Plus that's not how `super()` works, calling super twice doesn't mean it will call the next class in the MRO automatically. – Ashwini Chaudhary Feb 18 '15 at 06:41
  • this would be a special case. but for multiple inheritance when constructors dont take arguments, super calls base classes constructors from [left to right](http://stackoverflow.com/questions/3277367/how-does-pythons-super-work-with-multiple-inheritance) – Marcin Feb 18 '15 at 06:41
  • Thanks @AshwiniChaudhary... Updated the question for the second code – thiruvenkadam Feb 18 '15 at 06:45
  • And I mean that by calling super with different number of arguments, it throws a `TypeError` rather than trying to check whether any other method can fit the bill. So why go through super at all? – thiruvenkadam Feb 18 '15 at 06:48
  • You have mis-understood how `super` works, `super()` simply checks for the given method in the MRO chain, here it is going to be `Base1.__init__`, now it's up to `Base1.__init__`'s whether the next `__init__` can be called or not, if there's a `super()` call present, then it will look for `__init__` in classes that appear after `Base1` in Child's MRO, and that is `Base2` here. Read: https://rhettinger.wordpress.com/2011/05/26/super-considered-super/ – Ashwini Chaudhary Feb 18 '15 at 06:57

2 Answers2

16

super() in the face of multiple inheritance, especially on methods that are present on object can get a bit tricky. The general rule is that if you use super, then every class in the hierarchy should use super. A good way to handle this for __init__ is to make every method take **kwargs, and always use keyword arguments everywhere. By the time the call to object.__init__ occurs, all arguments should have been popped out!

class Base1(object):
    def __init__(self, a, **kwargs):
        print "In Base 1", a
        super(Base1, self).__init__()

class Base2(object):
    def __init__(self, **kwargs):
        print "In Base 2"
        super(Base2, self).__init__()

class Child(Base1, Base2):
    def __init__(self, **kwargs):
        super(Child, self).__init__(a="Something for Base1")

See the linked article for way more explanation of how this works and how to make it work for you!

Edit: At the risk of answering two questions, "Why use super at all?"

We have super() for many of the same reasons we have classes and inheritance, as a tool for modularizing and abstracting our code. When operating on an instance of a class, you don't need to know all of the gritty details of how that class was implemented, you only need to know about its methods and attributes, and how you're meant to use that public interface for the class. In particular, you can be confident that changes in the implementation of a class can't cause you problems as a user of its instances.

The same argument holds when deriving new types from base classes. You don't want or need to worry about how those base classes were implemented. Here's a concrete example of how not using super might go wrong. suppose you've got:

class Foo(object):
     def frob(self):
         print "frobbign as a foo"
class Bar(object):
     def frob(self):
         print "frobbign as a bar"

and you make a subclass:

class FooBar(Foo, Bar):
    def frob(self):
        Foo.frob(self)
        Bar.frob(self)

Everything's fine, but then you realize that when you get down to it, Foo really is a kind of Bar, so you change it

 class Foo(Bar):
     def frob(self):
         print "frobbign as a foo"
         Bar.frob(self)

Which is all fine, except that in your derived class, FooBar.frob() calls Bar.frob() twice.

This is the exact problem super() solves, it protects you from calling superclass implementations more than once (when used as directed...)

SingleNegationElimination
  • 151,563
  • 33
  • 264
  • 304
11

As for your first question, __init__ is neither a staticmethod nor a classmethod; it is an ordinary instance method. (That is, it receives the instance as its first argument.)

As for your second question, if you want to explicitly call multiple base class implementations, then doing it explicitly as you did is indeed the only way. However, you seem to be misunderstanding how super works. When you call super, it does not "know" if you have already called it. Both of your calls to super(Child, self).__init__ call the Base1 implementation, because that is the "nearest parent" (the most immediate superclass of Child).

You would use super if you want to call just this immediate superclass implementation. You would do this if that superclass was also set up to call its superclass, and so on. The way to use super is to have each class call only the next implementation "up" in the class hierarchy, so that the sequence of super calls overall calls everything that needs to be called, in the right order. This type of setup is often called "cooperative inheritance", and you can find various articles about it online, including here and here.

BrenBarn
  • 242,874
  • 37
  • 412
  • 384