Question
In Python, why is super() used in classes that inherit from another class? Is there a practical difference between calling Base.__init__(self) directly and using super().__init__() instead?
For example:
class Base(object):
def __init__(self):
print("Base created")
class ChildA(Base):
def __init__(self):
Base.__init__(self)
class ChildB(Base):
def __init__(self):
super(ChildB, self).__init__()
ChildA()
ChildB()
Both subclasses appear to initialize the base class. What is the difference, and why is super() generally recommended?
Short Answer
By the end of this page, you will understand what super() does in Python, how it helps subclasses call parent methods, and why it is usually safer and more maintainable than calling a parent class directly. You will also see how this becomes especially important in multiple inheritance.
Concept
In object-oriented Python, a subclass often wants to reuse behavior from its parent class. A common example is calling the parent class's __init__() method so that the parent can perform its own setup.
There are two main ways to do that:
Base.__init__(self)
or
super().__init__()
The key difference is this:
Base.__init__(self)explicitly calls one specific class.super().__init__()asks Python to call the next method in the method resolution order (MRO).
That makes super() more flexible and cooperative.
Why this matters
If your class hierarchy is simple and uses only single inheritance, both approaches may appear to work the same way. But in real codebases, inheritance chains can grow, and multiple inheritance can be involved. In those cases, directly naming a parent class can:
- skip other classes in the chain
- call the wrong method order
- cause duplicate initialization
- make code harder to maintain if the hierarchy changes
super() is designed to work with Python's inheritance system rather than against it.
What super() really means
A beginner-friendly way to think about super() is:
- not "call my parent"
- but "call the next matching method according to Python's inheritance order"
That distinction is very important.
In modern Python
In Python 3, you usually write:
super().__init__()
Older Python 2 style often looked like:
super(ChildB, self).__init__()
Both express the same idea, but the Python 3 version is shorter and cleaner.
Mental Model
Imagine a team relay race.
Each class in an inheritance chain is one runner. When setup work happens, the baton should be passed from one runner to the next in the correct order.
- Using
super()is like saying: pass the baton to the next runner in the official race order. - Using
Base.__init__(self)is like saying: ignore the race order and hand the baton directly to one specific runner.
In a simple two-runner race, both may seem fine. But in a bigger relay with more runners, directly choosing one runner can skip people, cause confusion, or make someone run twice.
So super() helps classes cooperate cleanly, especially when the inheritance chain is more complex.
Syntax and Examples
Core syntax
Direct parent call
class Base:
def __init__(self):
print("Base created")
class Child(Base):
def __init__(self):
Base.__init__(self)
print("Child created")
Using super()
class Base:
def __init__(self):
print("Base created")
class Child(Base):
def __init__(self):
super().__init__()
print("Child created")
Beginner-friendly example
class Animal:
def __init__(self, name):
self.name = name
print("Animal initialized")
class Dog(Animal):
def __init__(self, name, breed):
super().__init__(name)
self.breed = breed
print("Dog initialized")
pet = Dog("Rex", "Labrador")
print(pet.name)
print(pet.breed)
What this does
Animal.__init__()stores the sharednameDog.__init__()addsbreedsuper().__init__(name)letsAnimaldo its setup without rewriting that logic
Output:
Animal initialized
Dog initialized
Rex
Labrador
Why direct calls can be limiting
class Base:
def __init__(self):
print("Base")
class Child(Base):
def __init__(self):
Base.__init__(self)
print("Child")
This works, but Child is now tightly coupled to Base. If the inheritance structure changes later, this hardcoded call may become incorrect or incomplete.
Step by Step Execution
Trace example
class Base:
def __init__(self):
print("Base created")
class Child(Base):
def __init__(self):
print("Child setup started")
super().__init__()
print("Child setup finished")
Child()
Step by step
1. Child() is called
Python creates a new Child instance.
2. Child.__init__() runs
Execution enters the child's constructor:
print("Child setup started")
Output so far:
Child setup started
3. super().__init__() runs
Python looks at the method resolution order for Child.
For this class, the MRO is roughly:
Child -> Base -> object
So the next __init__() after Child is Base.__init__().
That prints:
Base created
4. Control returns to Child.__init__()
Now execution continues after super().__init__():
print("Child setup finished")
Final output:
Child setup started
Base created
Child setup finished
Important detail
In this simple case, super().__init__() ends up calling Base.__init__(). But the reason it does that is not because Base is hardcoded. It does so because Base is the next class in the MRO.
Real World Use Cases
Common practical uses of super()
Initializing shared fields in parent classes
class User:
def __init__(self, username):
self.username = username
class Admin(User):
def __init__(self, username, level):
super().__init__(username)
self.level = level
This avoids duplicating username setup in every subclass.
Extending framework classes
Many Python frameworks expect subclasses to call the parent implementation.
Examples include:
- GUI widgets
- test case classes
- Django forms or views
- custom exceptions or data models
A subclass may add custom behavior but still rely on the base class to do core setup.
Cooperative multiple inheritance
Mixins and reusable class components often rely on super() so each class in the chain gets a chance to run.
class LoggerMixin:
def __init__(self, *args, **kwargs):
print("Logger setup")
super().__init__(*args, **kwargs)
This pattern is common in larger Python applications.
Reusing parent methods beyond __init__()
super() is not only for constructors.
class Base:
def save(self):
print("Saving to database")
class AuditModel(Base):
def save(self):
print("Writing audit log")
super().save()
This lets a subclass extend behavior instead of replacing it entirely.
Real Codebase Usage
How developers use this in real projects
1. Extending a base class without breaking it
A subclass often adds fields or behavior but still depends on parent setup.
class BaseService:
def __init__(self, config):
self.config = config
class EmailService(BaseService):
def __init__(self, config, sender):
super().__init__(config)
self.sender = sender
2. Writing cooperative mixins
Mixins usually avoid naming parent classes directly. They call super() so they can work in different inheritance combinations.
class TimestampMixin:
def __init__(self, *args, **kwargs):
self.created_at = "now"
super().__init__(*args, **kwargs)
3. Following framework expectations
Framework base classes may perform registration, validation, resource setup, or default configuration in __init__(). If you skip super(), the object may be left in an invalid state.
4. Keeping classes maintainable
If a class later changes from:
class Child(Base):
to:
class Child(FeatureMixin, Base):
then super() can continue to work with the new MRO. A hardcoded Base.__init__(self) may ignore FeatureMixin completely.
5. Chaining behavior cleanly
Developers often combine super() with:
- validation before parent calls
- guard clauses
- post-processing after parent calls
- shared setup in abstract base classes
- error handling around initialization
Example:
class FileLoader(BaseLoader):
def __init__(self, path):
if not path:
raise ValueError("path is required")
super().__init__()
self.path = path
This keeps responsibility split clearly between child and parent classes.
Common Mistakes
1. Thinking super() means only "parent class"
It actually means the next class in the MRO.
That matters in multiple inheritance.
2. Hardcoding the base class name unnecessarily
Broken or inflexible style:
class Base:
def __init__(self):
print("Base")
class Child(Base):
def __init__(self):
Base.__init__(self)
Better:
class Child(Base):
def __init__(self):
super().__init__()
Why better:
- easier to maintain
- works better with inheritance changes
- supports cooperative multiple inheritance
3. Forgetting to pass arguments through
Broken example:
class Person:
def __init__(self, name):
self.name = name
class Student(Person):
def __init__(self, name, grade):
super().__init__()
self.grade = grade
This fails because Person.__init__() expects name.
Correct version:
class Student(Person):
def __init__(self, name, grade):
super().__init__(name)
self.grade = grade
4. Mixing direct base calls and super() in multiple inheritance
This can cause methods to run twice or be skipped.
5. Forgetting that all classes in a cooperative chain should also use super()
If one class in the chain directly calls a specific parent or does not forward arguments properly, the cooperative design can break.
6. Using old Python 2 print syntax in Python 3
The original example used:
print "Base created"
In Python 3, use:
print("Base created")
Comparisons
Base.__init__(self) vs super().__init__()
| Approach | What it does | Good for | Limitations |
|---|---|---|---|
Base.__init__(self) | Calls one specific class directly | Very simple cases | Tightly coupled, not cooperative, poor fit for multiple inheritance |
super().__init__() | Calls the next method in the MRO | Most modern Python class design | Requires understanding argument flow and MRO |
Single inheritance vs multiple inheritance
| Situation | Direct base call | super() |
|---|---|---|
| One parent class only | Often appears to work | Works and stays flexible |
| Multiple inheritance | Can skip classes or duplicate calls | Designed for this case |
| Future hierarchy changes | More fragile | More maintainable |
Old-style super() vs modern super()
| Syntax | Version | Example |
|---|---|---|
super(Child, self).__init__() | Common in Python 2, valid in Python 3 | Explicit form |
super().__init__() | Preferred in Python 3 | Shorter and cleaner |
Important takeaway
In small examples, direct calls and super() may produce the same visible output. The real advantage of super() is that it scales correctly as inheritance becomes more complex.
Cheat Sheet
Quick reference
Call a parent initializer in Python 3
super().__init__()
Pass arguments to the parent
super().__init__(name, age)
Old explicit form
super(MyClass, self).__init__()
Rules of thumb
- Prefer
super()over direct parent class calls - Use it in subclasses that extend parent behavior
- Remember:
super()follows the MRO, not just the immediate parent - In multiple inheritance, all participating classes should use
super()cooperatively - Forward
*argsand**kwargswhen building flexible mixins
Common patterns
Extend parent setup
class Child(Base):
def __init__(self):
super().__init__()
self.value = 10
Cooperative mixin style
class Mixin:
def __init__(self, *args, **kwargs):
do_setup()
super().__init__(*args, **kwargs)
Edge cases to remember
- Direct calls like
Base.__init__(self)bypass MRO - If parent
__init__()needs arguments, you must pass them - Not every overridden method must call
super(), but if the parent behavior matters, it usually should
FAQ
What does super() do in Python?
It returns a proxy that lets you call the next method in the class's method resolution order.
Is super().__init__() the same as Base.__init__(self)?
Not exactly. In simple single inheritance, they may behave similarly. But super() follows the MRO, while Base.__init__(self) directly targets one class.
Why is super() recommended in Python?
Because it is more maintainable, supports cooperative multiple inheritance, and adapts better if the class hierarchy changes.
Should I always use super() in __init__()?
Usually yes when extending a parent class's initialization. But whether you must call it depends on whether the parent class has important setup work.
Does super() only work with __init__()?
No. You can use it with any inherited method, such as save(), setup(), or run().
What is MRO in Python?
MRO stands for Method Resolution Order. It is the order Python uses to search for methods in an inheritance hierarchy.
Can super() be used in single inheritance?
Yes. It is common and recommended even there, because it keeps the code flexible and consistent.
Why do old examples use super(Child, self)?
That is older syntax commonly used in Python 2. In Python 3, super() without arguments is preferred.
Mini Project
Description
Build a small inheritance example for a notification system. This project demonstrates how a subclass can extend parent initialization using super(), while keeping shared setup logic in the base class. It is useful because many real applications have base classes for shared behavior and specialized subclasses for extra features.
Goal
Create a base notification class and a subclass that adds email-specific behavior while reusing the base class initialization with super().
Requirements
- Create a
Notificationclass that stores a message. - Create an
EmailNotificationclass that inherits fromNotification. - Use
super()insideEmailNotification.__init__()to initialize the shared message field. - Add an
emailfield in the subclass. - Print both the message and the email address from an instance of the subclass.
Keep learning
Related questions
@staticmethod vs @classmethod in Python Explained
Learn the difference between @staticmethod and @classmethod in Python with clear examples, use cases, mistakes, and a mini project.
Add Rows to a Pandas DataFrame in Python
Learn how to add rows to a Pandas DataFrame, why repeated row appends are slow, and when to use loc, concat, or record lists.
Call a Function by Name in a Python Module
Learn how to call a function by name in a Python module using strings, getattr, and safe patterns for dynamic function dispatch.