Python OOP Tutorial 4: Inheritance - Creating Subclasses
Based on Corey Schafer's video on YouTube. If you like this content, support the original creators by watching, liking and subscribing to their content.
Inheritance lets subclasses reuse parent attributes and methods while adding or overriding behavior without duplicating code.
Briefing
Python inheritance lets developers build specialized subclasses that reuse a parent class’s attributes and methods, then selectively override or extend behavior—without duplicating code or breaking the parent. Using an `Employee` class as the baseline, the tutorial creates `Developer` and `Manager` subclasses that automatically gain shared functionality like initialization, email handling, and salary-related methods, while each subclass adds its own customization.
The walkthrough starts by defining `Developer` as a subclass of `Employee` and leaving `Developer` initially empty. Instantiating `Developer` objects still works because Python searches for missing methods using the method resolution order (MRO). When `Developer` lacks an `__init__`, Python climbs the inheritance chain until it finds `Employee.__init__`. The `help()` function is used to make this search path visible, showing that `Developer` inherits methods such as `__init__`, `apply_raise`, and `full_name`, along with class attributes like `raise_amount` from `Employee`. The practical payoff is immediate: `Developer` instances can be created with the same inputs as `Employee` and can access the same inherited behavior.
Next comes customization. By changing `raise_amount` inside `Developer`, the tutorial demonstrates that `apply_raise` uses the subclass’s value for developer instances, while `Employee` instances remain unaffected. Switching an object back to `Employee` restores the original 4% raise behavior, reinforcing the separation between parent and subclass configuration.
The tutorial then addresses a common inheritance need: subclasses often require extra initialization data. `Developer` is modified so it accepts an additional argument—`programming_language`—even though `Employee.__init__` only accepts first name, last name, and pay. Rather than copying the entire parent `__init__` (which would harm maintainability), the subclass defines its own `__init__` and delegates shared initialization to the parent using `super().__init__(first, last, pay)`. After the parent sets the common fields, `Developer.__init__` stores `self.programming_language`. Instantiations confirm that both the inherited email setup and the new programming-language attribute are correctly populated.
A parallel example builds `Manager` as another subclass of `Employee`. `Manager.__init__` accepts a list of supervised employees (defaulting to `None` and then converting to an empty list inside the method to avoid mutable default arguments). The subclass adds methods to manage that list: `add_employee`, `remove_employee`, and `print_employees`, which prints each supervised employee’s full name. Creating a manager shows inherited attributes like email and salary behavior alongside the manager-specific methods.
Finally, the tutorial ties inheritance to Python’s type-checking tools: `isinstance` confirms whether an object belongs to a class, while `issubclass` checks class relationships. Both `Developer` and `Manager` are subclasses of `Employee`, but neither is a subclass of the other. The closing real-world example points to Python’s `exceptions` hierarchy—such as `HTTPException` inheriting from `Exception` and specialized HTTP errors inheriting from `HTTPException`—illustrating how inheritance scales code reuse as projects grow.
Cornell Notes
Inheritance in Python enables subclasses to reuse a parent class’s attributes and methods, then override or extend behavior. The tutorial uses an `Employee` base class and creates `Developer` and `Manager` subclasses that automatically inherit shared functionality via Python’s method resolution order (MRO). Customization works by overriding class attributes (e.g., `raise_amount`) so only subclass instances change behavior, while parent instances remain unchanged. When subclasses need extra initialization parameters, they define their own `__init__` and delegate common setup to the parent using `super().__init__`, keeping code DRY. The examples also show practical subclass methods for managing state (like a manager’s employee list) and how `isinstance`/`issubclass` reflect inheritance relationships.
How does Python successfully create a `Developer` object when `Developer` doesn’t define its own `__init__`?
What does changing `raise_amount` inside `Developer` accomplish, and why doesn’t it affect `Employee` instances?
Why use `super().__init__(first, last, pay)` when adding `programming_language` to `Developer`?
How does `Manager` handle a list of supervised employees without using a mutable default argument?
What’s the difference between `isinstance` and `issubclass` in the inheritance examples?
How does the exceptions hierarchy demonstrate inheritance in real code?
Review Questions
- When Python can’t find a method in a subclass, what mechanism determines where it searches next, and what role does MRO play?
- Why does overriding a class attribute in a subclass change behavior for that subclass’s instances but not for parent-class instances?
- What problem can arise from using a mutable list as a default argument, and how does the `Manager` example avoid it?
Key Points
- 1
Inheritance lets subclasses reuse parent attributes and methods while adding or overriding behavior without duplicating code.
- 2
Python’s method resolution order (MRO) determines how it finds missing methods like `__init__` by walking up the inheritance chain.
- 3
Overriding subclass class attributes (such as `raise_amount`) changes behavior for subclass instances while leaving parent instances unchanged.
- 4
When subclasses need extra initialization parameters, defining a subclass `__init__` and delegating shared setup to `super().__init__` keeps code DRY and maintainable.
- 5
Avoid mutable default arguments by using `None` and creating a new list inside `__init__` when needed.
- 6
`isinstance` checks object-to-class relationships, while `issubclass` checks class-to-class inheritance relationships.
- 7
Python’s built-in exception hierarchy provides a practical example of scalable inheritance for specialized error types.