Back to Blog
Python

python super **init** Explained

Learn how python super **init** works, why it matters for inheritance, and how to avoid common mistakes when calling parent constructors.

pythonsuperinitinheritancemro
Diagram showing Python class inheritance with super() in __init__

python super init requires a clear understanding of the core syntax, runtime behavior, and practical implementation patterns demonstrated in the examples below.

When you define a subclass in Python and want to initialize the parent class attributes, you typically call super().__init__() inside the child's __init__ method. But what exactly does super() do, and why is it the preferred way over directly naming the parent class? The answer lies in how Python resolves method calls across inheritance hierarchies, especially when multiple inheritance is involved.

What super() Actually Does in init

super() returns a proxy object that delegates method calls to the next class in the method resolution order (MRO). In the context of __init__, calling super().__init__(args) invokes the __init__ method of the next class in the MRO, not necessarily the direct parent. This is crucial for cooperative multiple inheritance, where classes are designed to work together in a linearized chain.

The MRO is computed using the C3 linearization algorithm, which ensures that each class appears after its bases and that the order is consistent across the hierarchy. You can inspect the MRO of any class with ClassName.__mro__. For a simple single-inheritance case, the MRO is straightforward: the child class, then the parent, then object. But with multiple inheritance, the order can be surprising if the hierarchy is not designed cooperatively.

Basic super().init Example

Here is a minimal example that demonstrates the standard use of super() in __init__:

class Parent: def __init__(self, name): self.name = name class Child(Parent): def __init__(self, name, age): super().__init__(name) self.age = age child = Child("Alice", 30) print(child.name, child.age) # Alice 30

In this example, super().__init__(name) calls Parent.__init__, which sets the name attribute. The child class then adds its own age attribute. This pattern is idiomatic because it avoids hardcoding the parent class name. If the parent class name changes, or if the child is later inserted into a different hierarchy, the code remains correct.

How super() Works with Multiple Inheritance and MRO

Consider a classic diamond problem:

class A: def __init__(self): print("A.__init__") super().__init__() class B(A): def __init__(self): print("B.__init__") super().__init__() class C(A): def __init__(self): print("C.__init__") super().__init__() class D(B, C): def __init__(self): print("D.__init__") super().__init__() d = D()

The output is:

D.__init__
B.__init__
C.__init__
A.__init__

Each class calls super().__init__(), which follows the MRO of D. The MRO is D -> B -> C -> A -> object. This cooperative behavior ensures that every __init__ in the chain is called exactly once, even though A appears only once in the MRO. If you had called B.__init__ directly from D, you would skip C.__init__ and potentially break the chain.

This is why super() is essential for cooperative multiple inheritance. It allows each class to delegate to the next class in the MRO, regardless of which class that happens to be. Without super(), you would need to manually orchestrate calls to every parent, which is brittle and error-prone.

Common Mistakes When Using super().init

One frequent mistake is calling the parent class directly instead of using super(). For example:

class Child(Parent): def __init__(self, name, age): Parent.__init__(self, name) # Not recommended self.age = age

This works in simple single inheritance, but it breaks in multiple inheritance because it bypasses the MRO. If Child is part of a larger hierarchy, other classes in the chain may never get initialized.

Another mistake is forgetting to call super().__init__() at all. If the parent class has required attributes or performs setup, the child instance may be left incomplete. This often leads to AttributeError when accessing those attributes later.

A third mistake is passing the wrong arguments to super().__init__(). In cooperative multiple inheritance, each class's __init__ should accept the arguments it needs and pass the rest along via **kwargs. This pattern is explained in the next section.

Passing Arguments Correctly with super().init

When multiple classes in the inheritance chain have different __init__ parameters, you need to design them to cooperate. The standard approach is to accept **kwargs in each __init__ and pass them along to super().__init__(). Here is an example:

class A: def __init__(self, a, **kwargs): self.a = a super().__init__(**kwargs) class B: def __init__(self, b, **kwargs): self.b = b super().__init__(**kwargs) class C(A, B): def __init__(self, **kwargs): super().__init__(**kwargs) c = C(a=1, b=2) print(c.a, c.b) # 1 2

In this design, each class extracts the keyword argument it needs and forwards the remaining **kwargs to the next class in the MRO. This works because the MRO is C -> A -> B -> object, and each __init__ is called in order. If you use positional arguments instead, you must ensure the order matches the MRO, which is fragile. Keyword arguments with **kwargs are the recommended way to support cooperative multiple inheritance.

When to Avoid super().init

There are cases where calling super().__init__() is not appropriate. For example, if you intentionally want to bypass the parent class initialization—perhaps to implement a completely different setup—you can omit the call. However, this is rare and usually indicates a design issue. Another case is when working with legacy code that does not follow cooperative patterns. If a parent class does not call super().__init__() itself, the chain may break. In such situations, you may need to call the parent directly, but you should be aware that this will not work correctly in a multiple-inheritance context.

Maintainability and Compatibility Considerations

Using super() consistently makes your code more maintainable because it decouples the child class from the specific parent class name. This is particularly valuable in frameworks and libraries where classes may be subclassed by users. It also aligns with Python's data model and the recommended practices described in the language documentation.

Regarding compatibility, all modern Python 3 versions support super() with no arguments inside a class definition. In Python 2, you had to call super(Child, self).__init__(), but that is no longer relevant for new code. If you are maintaining legacy Python 2 code, you may encounter that pattern, but for any new development, the zero-argument form is the standard.

From a performance perspective, there is no significant difference between calling super().__init__() and directly calling the parent class. The overhead of creating the proxy object is negligible compared to the actual initialization work. The real benefit of super() is correctness and maintainability, not speed. When you have a deep inheritance chain, the MRO ensures that each __init__ is called exactly once, which is more efficient than manually calling multiple parent constructors and risking duplicate initialization.

In summary, python super **init** is a fundamental pattern for Python class inheritance. It ensures that parent classes are initialized correctly, supports cooperative multiple inheritance, and keeps your code flexible and robust. By understanding how super() interacts with the MRO and by following the **kwargs convention, you can avoid common pitfalls and write classes that work well in complex hierarchies.

python super **init** Explained | RYUSLOG DEV