Inheritance & super() (MRO)
Overview
Inheritance allows a new class to inherit attributes and methods from an existing class, establishing an 'is-a' relationship. The `super()` function returns a proxy object that delegates method calls to a parent or sibling class. Think of inheritance like a master template and `super()` as a phone line explicitly calling the master template to avoid repeating setup logic. When overriding `__init__`, calling `super().__init__()` ensures the parent's initialization runs.
It reduces code duplication by centralizing shared logic in base classes, making it easier to maintain and extend functionality.
Where used: Django models (`models.Model`), Pydantic BaseModels, FastAPI API routers
Why learn this
- Understanding how third-party framework classes (like Django Views) work under the hood
- Extending existing exceptions by subclassing `Exception`
- Avoiding duplicated boilerplate across related data structures
Code walkthrough
class Worker:
def start(self):
print('worker started')
class CeleryWorker(Worker):
def start(self):
super().start()
print('connected to broker')
w = CeleryWorker()
w.start()
Focus: super().start()
Aha moment
class APIError(Exception):
def __init__(self, code):
self.code = code
class NotFoundError(APIError):
def __init__(self, code):
pass
err = NotFoundError(404)
print(hasattr(err, 'code'))
Prediction: What does this print: True or False?
Common guess: True
Because `NotFoundError` overrides `__init__` and does NOT call `super().__init__(code)`, the `APIError`'s `__init__` is skipped, so `self.code` is never set.
Common mistakes
- Forgetting to call super() in __init__: The parent's attributes are never initialized, leading to an `AttributeError` when those attributes are accessed.
- Redefining parent behavior instead of extending it: Completely overwriting a parent method without calling `super()` drops the parent's original functionality when you only meant to add to it.
Glossary
- proxy object
- A special object that stands in for another object, forwarding or delegating commands to it. Example: `super()` returns a proxy object so that `super().__init__()` calls the parent class's `__init__` without naming the parent directly.
- base classes
- The original or parent classes that other classes are built upon and inherit features from. Example: in `class Dog(Animal):`, `Animal` is the base class.
Recall questions
- What is the primary purpose of the `super()` function in Python?
- What happens if you override the `__init__` method in a child class but do not call `super().__init__()`?
- What kind of relationship does inheritance establish between a parent and child class?
Understanding checks
What happens when a child class overrides `__init__` but forgets to call `super().__init__()`?
The parent's initialization logic will be skipped.
This is dangerous because any attributes that the parent normally sets up in its `__init__` won't be created, leading to an `AttributeError` when other inherited methods try to access them.
What does this code print?
It prints `A` followed by `B`.
The call to `b.ping()` runs the `ping` method in `B`. Inside `B.ping`, `super().ping()` delegates to the parent class `A`, printing 'A'. Then it resumes in `B` and prints 'B'.
Practice tasks
Extend an API Request class
Modify the `SecureRequest` class so it takes a `url` and a `token`. Call `super().__init__(url)` to handle the URL, then store the `token` as an instance variable.
Challenge
Implement a Database Model hierarchy
Create a base class `Model` with a `save` method printing 'saving to db'. Create a `UserModel` inheriting from `Model` whose `save` prints 'validating user' then calls the parent's `save` using `super()`.
Continue learning
Previous: instance / class / static methods