Method Overriding in Python

Learn how to override parent class methods in Python, when to extend behavior with super() versus replace it entirely, and patterns for clean method overriding.

6 min read

Python method overriding is the mechanism by which a child class provides its own implementation of a method that already exists in its parent class. When you define a method in a child class with the same name as a method in the parent, the child's version takes priority. Any call to that method on an instance of the child class executes the child's implementation, not the parent's. This is not a bug or a collision; it is the primary way that object-oriented programs customize inherited behavior. The parent provides a general implementation that works for the common case, and each child overrides specific methods to handle its own specialized requirements. The rest of the parent's methods, the ones the child does not override, continue to work exactly as they did in the parent, giving the child a blend of inherited and customized behavior.

The article on single inheritance in Python covered the mechanics of creating a child class and extending the parent's constructor. Overriding extends that pattern to every method in the class. Any method the parent defines, from the constructor to utility helpers to the main public API, can be overridden in the child. The decision to override a method rather than use the inherited version is a design choice that depends on whether the parent's implementation meets the child's needs as-is or needs to be adapted.

There are two fundamental patterns for method overriding. The extending pattern calls the parent's version through super() and adds behavior before or after that call. The replacing pattern omits the super() call entirely and provides a completely new implementation. The extending pattern is more common and more maintainable because it preserves the parent's behavior and builds on it. The replacing pattern is appropriate when the child's behavior is fundamentally different from the parent's and running the parent's version would produce incorrect results. This article covers both patterns with examples and explains how to choose between them.

The extending pattern: building on the parent's behavior

The extending pattern is the most common form of method overriding. The child's method calls super().method_name() to let the parent do its work, then adds its own logic before or after the parent call. This pattern ensures that the parent's behavior is preserved, which means the child benefits from any future improvements to the parent's implementation and maintains compatibility with any code that expects the parent's behavior as a baseline.

Consider a parent class that formats a price for display and a child class that adds a currency symbol. The parent handles the numeric formatting, and the child adds the symbol without duplicating the formatting logic:

pythonpython
class PriceFormatter:
    def format(self, amount):
        return f"{amount:.2f}"
 
class USDollarFormatter(PriceFormatter):
    def format(self, amount):
        numeric = super().format(amount)
        return f"${numeric}"
 
class EuroFormatter(PriceFormatter):
    def format(self, amount):
        numeric = super().format(amount)
        return f"€{numeric}"

Each child class overrides the format method, calls super().format() to get the parent's formatted number, and prepends the appropriate currency symbol. If the parent's formatting logic ever changes, for example to use commas for thousands separators, both child classes automatically pick up the new behavior because they call through super() rather than duplicating the parent's numeric formatting. The extending pattern keeps the formatting logic in one place, the parent, and the currency logic in each child.

The order of operations in the extending pattern matters. You can call super() first and then add child-specific logic, or do child-specific work first and then call super(). The choice depends on whether the child's addition should appear before or after the parent's result, or whether the child needs to prepare data before passing it to the parent. The principle is the same in both cases: super() preserves the parent's behavior, and the child's code adds to it.

The replacing pattern: a completely new implementation

The replacing pattern is used when the child's behavior is so different from the parent's that calling the parent's version would produce incorrect results or duplicate work. In this pattern, the child's method does not call super() at all. It provides a complete implementation that stands on its own. The replacing pattern is less common than the extending pattern, and it should be used deliberately because it breaks the chain of cooperative behavior that super() enables.

A legitimate use of the replacing pattern is when the parent provides a default implementation that computes a value in a general way, and the child knows a more efficient algorithm for its specific case. A parent Shape class might compute area using a general polygon algorithm, and a child Circle class might override area with the simpler and more accurate pi times radius squared formula. Calling the parent's area method would produce the correct result but would waste computation, so the child replaces it entirely.

The replacing pattern should not be used to change the fundamental meaning of a method. If the parent's method is called calculate_total and it sums the prices of items in an order, a child that overrides it to return the number of items instead of the total price is violating the contract established by the method name. Code that calls calculate_total expects a monetary amount, and receiving a count instead will cause bugs that are hard to trace because the method name suggests the wrong behavior. If a child needs fundamentally different behavior, it should define a new method with a descriptive name rather than misusing an inherited name.

Overriding the constructor

The constructor is the most frequently overridden method in object-oriented Python, and the pattern is always the extending pattern. The child's constructor must call super().init() with the arguments the parent expects, then add its own attribute assignments. Omitting the super() call means the parent's attributes are never initialized, and any inherited method that tries to read those attributes will fail with an AttributeError.

Here is a parent class with two attributes and a child class that adds a third:

pythonpython
class Employee:
    def __init__(self, name, department):
        self.name = name
        self.department = department
 
class Manager(Employee):
    def __init__(self, name, department, team_size):
        super().__init__(name, department)
        self.team_size = team_size

The Manager constructor accepts the parent's required parameters plus team_size. It calls super().init() to let Employee set name and department, then sets team_size on its own. Any method inherited from Employee that reads self.name or self.department will work correctly on a Manager object because the parent's constructor ran. The article on Python constructors covers initialization patterns in detail, and the super() pattern described here is the standard way to extend initialization in child classes.

Overriding and the Liskov substitution principle

A well-designed override should satisfy the Liskov substitution principle, which states that objects of a child class should be usable anywhere objects of the parent class are expected, without the calling code needing to know which specific type it is working with. In practical terms, this means an overridden method should accept the same parameters as the parent's method, or more permissive ones, and should return a value that is compatible with what the parent's method returns.

If the parent's method accepts a single string argument and returns an integer, the child's override should also accept a string and return an integer. Accepting a string or None as the argument is acceptable because code that passes a string still works. Returning a float instead of an integer is usually acceptable because code that expects a number can work with either. Returning a string when the parent returns an integer is a violation because code that calls the method and performs arithmetic on the result will break. These compatibility rules are not enforced by Python, but following them makes your class hierarchies predictable and safe to use in larger systems.

The article on instance methods in Python covers how methods receive and use the self parameter, and that mechanism works identically whether the method is defined on a parent class or overridden in a child. The self parameter always refers to the object the method was called on, regardless of which class in the hierarchy defined the method that is actually executing.

Rune AI

Rune AI

Key Insights

  • Method overriding means a child class defines a method with the same name as a parent method, and the child's version takes priority.
  • Use super().method_name() inside the overriding method to call the parent's version, chaining behavior rather than replacing it.
  • The extending pattern does child-specific work before or after calling super(); the replacing pattern omits the super() call entirely.
  • Always call super().init() in an overridden constructor to ensure parent attributes are initialized.
  • Override methods to specialize behavior, not to change the fundamental meaning of the method name.
RunePowered by Rune AI

Frequently Asked Questions

What is method overriding in Python?

Method overriding is when a child class defines a method with the same name as a method in its parent class. When the method is called on an instance of the child class, the child's version runs instead of the parent's. Overriding lets child classes specialize or replace behavior that the parent defined in a general way, and it is one of the primary mechanisms for customizing inherited behavior in object-oriented programming.

How do I call the parent's version of an overridden method?

Use the super() function inside the child's method. Calling super().method_name() invokes the parent's implementation of that method, even though the child has overridden it. This pattern is called extending rather than replacing, and it lets the child do its own work before or after delegating to the parent for the shared logic. It is the most common overriding pattern in well-designed class hierarchies.

Can I override the __init__ method in a child class?

Yes, and doing so is standard practice. The child's __init__ should call super().__init__() with the arguments the parent expects, then add its own attribute assignments. This ensures the parent's initialization logic runs before the child extends it. If you override __init__ without calling super().__init__(), the parent's attributes will not be set, which will likely cause errors when inherited methods try to access them.

Conclusion

Method overriding is how child classes make inherited behavior their own. The extending pattern, where the child calls super() to let the parent do its work and then adds specialized logic, is the most common and most maintainable approach. The replacing pattern, where the child completely replaces the parent's implementation, is appropriate when the child's behavior is fundamentally different. Understanding when to use each pattern and how to use super() correctly makes your class hierarchies both flexible and predictable.