Inheritance lets a child class reuse its parent’s code. But real programs often need the child to behave differently in specific spots while keeping everything else intact. That is where overriding and
super() come together.The mental model
Overriding a method
class Vehicle:
def start(self):
return "Engine roars"
class ElectricCar(Vehicle):
def start(self):
return "Silent hum"start entirely.When you call
my_car.start(), Python looks at the object’s actual class first. Because ElectricCar defines its own start, that version wins and Vehicle.start is never touched for this instance.Using super() to extend, not replace
class Employee:
def __init__(self, name):
self.name = name
class Manager(Employee):
def __init__(self, name, team_size):
super().__init__(name)
self.team_size = team_sizesuper().__init__() runs the parent’s setup before adding team_size.Without that line, a Manager object would have no
.name, and any method expecting it would crash. The call hands control to the parent constructor with exactly the arguments it needs, then returns so you can finish your own setup.Calling the parent’s behaviour inside an override
class Shape:
def describe(self):
return "generic shape"
class Square(Shape):
def describe(self):
base = super().describe()
return f"{base} with four equal sides"super().describe() fetches the parent’s string, then you build on it.The example shows a pattern: grab what the parent already gives you, append or transform it, and return the richer result. You keep DRY code instead of copying the parent logic into every child class.
A common mistake
You will see this pattern everywhere: base classes for API clients that set up auth headers in
__init__, then subclasses add rate-limiting; logging handlers where a child formats messages differently but still calls the parent’s write. Master overriding and super() now, and those extensions feel natural instead of fragile.