Python Overriding Inherited Method: Syntax & Behavior
python overriding inherited method: Learn how to override inherited methods in Python, use super() correctly, handle special methods, and avoid common inheritance pitf...
Overriding an inherited method in Python is the mechanism by which a subclass replaces a parent's method with its own implementation. Python overriding inherited method behavior requires no special keyword: define a method with the same name in the subclass, and the subclass version wins at runtime. This is the foundation of polymorphism, allowing callers to interact with a common interface while each subclass supplies its own behavior.
The Basic Syntax of Overriding
Overriding requires no special keyword or decorator. Define a method in the subclass with the same name as the parent's method:
class Animal: def speak(self) -> str: return "Some sound" class Dog(Animal): def speak(self) -> str: return "Woof" class Cat(Animal): def speak(self) -> str: return "Meow"
When you call speak() on a Dog or Cat instance, Python uses the subclass's version. The parent's speak is never invoked unless explicitly requested. The method signature does not need to match the parent's, but keeping it compatible matters for code that relies on the interface.
Calling the Parent Implementation with super()
An override often needs to extend the parent's behavior rather than replace it entirely. The super() function returns a proxy that lets you call the parent's version of the method:
class Vehicle: def __init__(self, brand: str): self.brand = brand def describe(self) -> str: return f"Vehicle: {self.brand}" class Car(Vehicle): def __init__(self, brand: str, model: str): super().__init__(brand) self.model = model def describe(self) -> str: return f"{super().describe()} | Model: {self.model}"
super().__init__(brand) runs the parent's constructor so self.brand is set before the subclass adds self.model. Calling super().describe() inside the override reuses the parent's formatting and appends the model. This pattern keeps shared initialization and formatting logic in one place instead of duplicating it.
Overriding init and Special Methods
The same mechanism applies to special methods. Overriding __init__ is the most common case because subclasses usually need to initialize additional state. Overriding __repr__, __str__, or __eq__ is equally straightforward:
class Temperature: def __init__(self, celsius: float): self.celsius = celsius def __repr__(self) -> str: return f"Temperature({self.celsius})" class Fahrenheit(Temperature): def __init__(self, fahrenheit: float): super().__init__((fahrenheit - 32) * 5 / 9) def __repr__(self) -> str: return f"Fahrenheit({self.celsius * 9 / 5 + 32})"
The subclass converts Fahrenheit to Celsius internally, stores it in self.celsius via the parent's __init__, and overrides __repr__ to display the value back in Fahrenheit. The conversion logic lives in one place, and the parent's invariants still hold.
Common Mistakes When Overriding
The most frequent error is forgetting to call super().__init__() in an override. If the parent's constructor sets required attributes, skipping the call leaves the instance partially initialized:
class Account: def __init__(self, owner: str): self.owner = owner self.balance = 0.0 class SavingsAccount(Account): def __init__(self, owner: str, interest_rate: float): # Missing super().__init__(owner) self.interest_rate = interest_rate
Accessing savings.balance or savings.owner raises AttributeError. The fix is to call super().__init__(owner) before setting subclass-specific state.
Another mistake is changing the method signature in a way that breaks callers. If the parent's method accepts a parameter and the override drops it, code that calls the method through the parent type fails. Python does not enforce signature compatibility at runtime, so the error appears only when the method is called.
A subtler issue is overriding a method but never calling the parent's version when the parent's behavior is still required. If the parent performs validation, logging, or cleanup, skipping super() silently removes that behavior.
Method Resolution Order and Multiple Inheritance
Python resolves which method to call using the method resolution order (MRO), computed with the C3 linearization algorithm. For single inheritance, the MRO is straightforward: the subclass, then the parent, then the grandparent. With multiple inheritance, the order matters:
class A: def action(self) -> str: return "A" class B(A): def action(self) -> str: return "B" class C(A): def action(self) -> str: return "C" class D(B, C): pass
D inherits action from B because B appears before C in the base list. Calling D().action() returns "B". Inspect the MRO with D.__mro__ to see the exact resolution order. This becomes relevant when overrides in multiple parents interact, and super() calls chain through the MRO rather than jumping directly to the first parent.
Maintainability and Runtime Considerations
Overriding changes runtime behavior, so the override must preserve the parent's contract. The Liskov substitution principle says a subclass should be usable anywhere its parent is expected. If an override narrows accepted input, changes the return type, or raises new exceptions, code that works with the parent may break with the subclass.
Keep overrides focused. If an override grows beyond a few lines, consider whether the parent's method should be refactored or whether composition is a better fit. Overriding also adds a runtime lookup cost: each method call goes through the MRO. In performance-sensitive loops, this overhead is usually negligible, but deep inheritance chains with many overrides add measurable indirection.
When overriding is not the right tool, composition often is. If a class needs behavior from another class but does not have an "is-a" relationship, holding an instance and delegating calls avoids the coupling that inheritance introduces. Overriding is the right choice when the subclass genuinely specializes the parent's behavior and the parent's contract remains intact.