logo
Send your CVContact

/

logo
Background image

Blog

__init__(self) and base class

Dawid Trendota 28.09.2018

Python is an object-oriented language that allows for multi-inheritment. How do derived classes behave in the __init__? We will discuss this in examples that often occur during conversations.

What will be the visible effect of this program?

class Base(object):
  def __init__(self):
    print("Base.__init__")
 
Class Derived(Base):
  def __init__(self):
    print("Derived.__init__")
 
derived = Derived()

Of course, the correct answer is:

> Derived.__init__

repl.it

This is the simplest possible case. The inheritance hierarchy is not rich. We have two classes and a simple relationship between them. What can lose us here is our knowledge of other object-oriented languages such as C++ and Java, and the treatment of __init__ as the equivalent constructor of those languages. Here, base class methods are not called automagically. To quote the documentation:

If a base class has an __init__() method, the derived class’s __init__() method, if any, must explicitly call it to ensure proper initialization of the base class part of the instance

We already have the correct answer, and its very precise justification. However, this is usually not enough for a conversation. The question is:

How do I ensure that the base class method is also called?

Here are some correct answers. Not all of them are equally good, but everyone is worth knowing.

Direct call to the __init__

Of course, any method can be called directly.

Class Derived(Base):
  def __init__(self):
    Base.__init__(self)
    print("Derived.__init__")

repl.it

Which doesn’t work too well with multiple inheritances. It is enough to gently expand our example to see how burdensome it is to call a method for each class we inherit.

class BaseA(object):
  def __init__(self):
    print("BaseA.__init__")
 
class BaseB(object):
  def __init__(self):
    print("BaseB.__init__")

class Derived(BaseA, BaseB):
  def __init__(self):
    BaseA.__init__(self)
    BaseB.__init__(self)
    print("Derived.__init__")

derived = Derived()

repl.it

Any change to the inheritance hierarchy here carries the risk of these calls being foreen. How can this be improved?

Using the super function

The super function is designed to make it easier to use methods overwritten in a derived class and make code easier to maintain.

Class Derived(Base):
  def __init__(self):
    super(Derived, self).__init__()
    print("Derived.__init__")

repl.it

Here everything turned out to be trouble-free. Now let’s go back to our example with multiple inheritance and correct it a little.

class BaseA(object):
  def __init__(self):
    print("BaseA.__init__")
 
class BaseB(object):
  def __init__(self):
    print("BaseB.__init__")

class Derived(BaseA, BaseB):
  def __init__(self):
    super(Derived, self).__init__()
    print("Derived.__init__")

derived = Derived()

However, the result is far from our expectations.

> BaseA.__init__
> Derived.__init__

repl.it

How does super work? Calls the next function in the MRO chain. The MRO mechanism itself is the material for the next article, here it is enough for us to ensure that the code is consistent. Each class should use the same mechanism.

class BaseA(object):
  def __init__(self):
    super(BaseA, self).__init__()
    print("BaseA.__init__")
 
class BaseB(object):
  def __init__(self):
    super(BaseB, self).__init__()
    print("BaseB.__init__")

class Derived(BaseA, BaseB):
  def __init__(self):
    super(Derived, self).__init__()
    print("Derived.__init__")

derived = Derived()

This ensures that __init__ be called correctly for each class.

> BaseB.__init__
> BaseA.__init__
> Derived.__init__

repl.it


Is there anything else that can be improved here?

New Super

Using python 3, you can use the simplified super syntax by opting out of entering the class and instance name (if someone is interested in a more specific document – PEP 3135 — New Super).

class Base(object):
  def __init__(self):
    print("Base.__init__")
 
Class Derived(Base):
  def __init__(self):
    super().__init__()
    print("Derived.__init__")
 
derived = Derived()

repl.it

Apart from the fact that such a call is more pleasant to the eye – it is also less prone to errors. Whenever you can, avoid hardcoding values. Also class names.

logo
ContactSend CV