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:
__init__
method a static method or a class method?super
over the second way(other than writing the base class name with the method call)__init__ method "__init__" is a reseved method in python classes. It is called as a constructor in object oriented terminology. This method is called when an object is created from a class and it allows the class to initialize the attributes of the class.
As for your first question, __init__ is neither a staticmethod nor a classmethod; it is an ordinary instance method.
When you create a new object of a class, Python automatically calls the __init__() method to initialize the object's attributes. Unlike regular methods, the __init__() method has two underscores (__) on each side. Therefore, the __init__() is often called dunder init.
The purpose of the __init__() method is to initialize the class. It is usually responsible for populating the instance variables. Because of this, you want to have __init__() get called for all classes in the class hierarchy.
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...)
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With