python
inheritance
object-oriented programming
parent initializers
duplicate question

Chain-calling parent initialisers in python

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

In Python, chain-calling parent initializers is done with super().__init__(). This calls the parent class's __init__ method, ensuring proper initialization up the inheritance chain. For single inheritance, super() calls the direct parent. For multiple inheritance, super() follows the Method Resolution Order (MRO), which ensures each class in the hierarchy is initialized exactly once. Always use super() instead of calling parent classes by name to maintain correct MRO behavior.

Basic super().init()

python
1class Animal:
2    def __init__(self, name):
3        self.name = name
4        print(f"Animal.__init__({name})")
5
6class Dog(Animal):
7    def __init__(self, name, breed):
8        super().__init__(name)  # Calls Animal.__init__
9        self.breed = breed
10        print(f"Dog.__init__({name}, {breed})")
11
12dog = Dog("Rex", "Labrador")
13# Output:
14# Animal.__init__(Rex)
15# Dog.__init__(Rex, Labrador)

super().__init__(name) calls the parent Animal.__init__, passing name. The child class then sets its own attributes.

Multi-Level Inheritance

python
1class Animal:
2    def __init__(self, name):
3        self.name = name
4        print(f"Animal.__init__({name})")
5
6class Pet(Animal):
7    def __init__(self, name, owner):
8        super().__init__(name)  # Calls Animal.__init__
9        self.owner = owner
10        print(f"Pet.__init__({name}, {owner})")
11
12class Dog(Pet):
13    def __init__(self, name, owner, breed):
14        super().__init__(name, owner)  # Calls Pet.__init__
15        self.breed = breed
16        print(f"Dog.__init__({name}, {owner}, {breed})")
17
18dog = Dog("Rex", "Alice", "Labrador")
19# Output:
20# Animal.__init__(Rex)
21# Pet.__init__(Rex, Alice)
22# Dog.__init__(Rex, Alice, Labrador)

Each super().__init__() call chains up to the next parent, all the way to the top of the hierarchy.

Multiple Inheritance and MRO

With multiple inheritance, super() follows the Method Resolution Order (MRO):

python
1class A:
2    def __init__(self):
3        super().__init__()
4        print("A.__init__")
5
6class B(A):
7    def __init__(self):
8        super().__init__()
9        print("B.__init__")
10
11class C(A):
12    def __init__(self):
13        super().__init__()
14        print("C.__init__")
15
16class D(B, C):
17    def __init__(self):
18        super().__init__()
19        print("D.__init__")
20
21d = D()
22# Output:
23# A.__init__
24# C.__init__
25# B.__init__
26# D.__init__
27
28# Check MRO
29print(D.__mro__)
30# (D, B, C, A, object)

super() in D calls B.__init__, which calls C.__init__ (not A.__init__), which then calls A.__init__. The MRO ensures A.__init__ is called only once, even though both B and C inherit from A.

Using **kwargs for Cooperative Multiple Inheritance

When classes in a multiple inheritance hierarchy have different __init__ parameters, use **kwargs to pass arguments through the chain:

python
1class Base:
2    def __init__(self, **kwargs):
3        # Consume no arguments, just call super
4        super().__init__(**kwargs)
5
6class Named(Base):
7    def __init__(self, name="", **kwargs):
8        super().__init__(**kwargs)
9        self.name = name
10
11class Aged(Base):
12    def __init__(self, age=0, **kwargs):
13        super().__init__(**kwargs)
14        self.age = age
15
16class Person(Named, Aged):
17    def __init__(self, **kwargs):
18        super().__init__(**kwargs)
19
20p = Person(name="Alice", age=30)
21print(p.name)  # Alice
22print(p.age)   # 30

Each class extracts its own parameters and passes the rest via **kwargs. This is the cooperative multiple inheritance pattern.

You can call a parent class by name instead of using super():

python
1class Dog(Pet):
2    def __init__(self, name, owner, breed):
3        # Explicit parent call — bypasses MRO
4        Pet.__init__(self, name, owner)
5        self.breed = breed

This works for single inheritance but breaks with multiple inheritance because it bypasses the MRO and can cause parent classes to be initialized multiple times or not at all.

The Diamond Problem

python
1class A:
2    def __init__(self):
3        print("A init")
4
5class B(A):
6    def __init__(self):
7        A.__init__(self)  # Direct call, not super()
8        print("B init")
9
10class C(A):
11    def __init__(self):
12        A.__init__(self)  # Direct call, not super()
13        print("C init")
14
15class D(B, C):
16    def __init__(self):
17        B.__init__(self)
18        C.__init__(self)
19        print("D init")
20
21d = D()
22# Output:
23# A init    <- called from B
24# B init
25# A init    <- called AGAIN from C (duplicate!)
26# C init
27# D init
28
29# With super(), A.__init__ is called only once

Using super() throughout avoids the diamond problem by following the MRO.

Python 2 vs Python 3

python
1# Python 2 (old syntax)
2class Dog(Animal):
3    def __init__(self, name, breed):
4        super(Dog, self).__init__(name)  # Must pass class and self
5        self.breed = breed
6
7# Python 3 (new syntax — preferred)
8class Dog(Animal):
9    def __init__(self, name, breed):
10        super().__init__(name)  # No arguments needed
11        self.breed = breed

Python 3's super() with no arguments is syntactic sugar that automatically determines the correct class and instance.

Skipping a Parent's init

Sometimes you want to skip the immediate parent and call a grandparent:

python
1class A:
2    def __init__(self):
3        self.x = 1
4
5class B(A):
6    def __init__(self):
7        super().__init__()
8        self.y = 2
9
10class C(B):
11    def __init__(self):
12        # Skip B's __init__, call A's directly
13        A.__init__(self)
14        self.z = 3
15
16c = C()
17print(c.x)  # 1
18# print(c.y)  # AttributeError — B.__init__ was skipped

This is generally discouraged because it breaks the initialization chain and may leave the object in an inconsistent state.

Common Pitfalls

  • Forgetting to call super().__init__(): If a child class does not call super().__init__(), the parent's attributes are never set. The child instance will be missing parent attributes, causing AttributeError later.
  • Calling parent by name in multiple inheritance: ParentClass.__init__(self, ...) bypasses the MRO and causes the diamond problem — a shared ancestor may be initialized multiple times. Always use super() in multiple inheritance hierarchies.
  • Mismatched init signatures: If Parent.__init__ takes (self, x) and Child.__init__ takes (self, x, y), you must correctly pass x to super().__init__(x). Forgetting to pass required arguments causes TypeError.
  • Order of super() call matters: Calling super().__init__() at the beginning vs end of __init__ affects when parent attributes are set. If child code depends on parent attributes, call super() first. If parent depends on child attributes, call super() last (rare).
  • **Not using kwargs in cooperative inheritance: Without **kwargs, classes in a multiple inheritance chain cannot pass unknown arguments through to other parents. Each class should accept and forward **kwargs for cooperative behavior.

Summary

  • Use super().__init__() to call parent initializers in Python 3
  • super() follows the Method Resolution Order (MRO), ensuring each class is initialized once
  • For multiple inheritance, use the **kwargs pattern for cooperative initialization
  • Avoid calling parent classes by name (Parent.__init__(self)) — it bypasses MRO
  • Check the MRO with ClassName.__mro__ or ClassName.mro() when debugging initialization order

Course illustration
Course illustration

All Rights Reserved.