Inheritance and polymorphism
Reuse a base class, override the parts that differ, and let method lookup at call time pick the right behaviour.
Duplication is the enemy of change. If two classes share most of their behaviour and differ in one method, inheritance lets you write the common part once in a base class and override only what changes. The payoff is not the typing saved; it is that one call site can work with objects of many different classes.
Same message, different response
Polymorphism means the caller writes shape.area() without caring which
concrete type shape is. Python looks up area on the object's class at call
time and runs whichever definition it finds first. The object decides, not
the caller.
The panel below builds a diamond hierarchy, D(B, C) with both bases deriving
from A, and shows the method resolution order that decides which who()
runs.
Choose which classes define who(), then call it on a D and follow the lookup
who() defined in
D.who()
MRO of D (left to right)
who() resolved to A.who
first class in the MRO that defines the method
B and C both derive from A, so the linearization has to put A after both — never before a subclass. That single constraint is what stops a shared base from quietly winning over an override.
Polymorphism is just this lookup happening at call time: the same obj.who() runs different code depending on the object's class, and the MRO decides which one when inheritance branches.
Overriding and super()
A subclass inherits every method of its base. To replace one, define a method
with the same name; to extend it, call the base version with super():
class Animal:
def __init__(self, name):
self.name = name
def speak(self):
return "..."
class Dog(Animal):
def speak(self): # overrides Animal.speak
return "Woof"Dog("Rex").speak() returns "Woof"; Animal("x").speak() still returns
"...". The base class is untouched, and existing callers keep working.
The method resolution order
With single inheritance the lookup is a straight line: class, then base, then
object. With multiple inheritance, super() and attribute lookup follow the
MRO, computed by the C3 linearization. The ordering guarantees a subclass
comes before its bases and a base appears before its own superclass — which is
why the diamond above always puts A after both B and C.
Diamonds need cooperative __init__
If two bases both define __init__ and neither calls super().__init__(...),
the second one is silently skipped. Each __init__ should call
super().__init__(**kwargs) so the chain reaches every class in the MRO
exactly once. Prefer composition when the hierarchy stops being obvious.
Duck typing
Python does not require a shared base class for polymorphism. It only requires that the object answers the message:
If it walks like a duck and quacks like a duck, treat it as a duck.
Any object with an area() method works where an area is expected, regardless
of its ancestry. Inheritance is a tool for reuse; polymorphism comes from the
protocol, not the class tree.
Illustrative vs real
The explorer pips every class into a single method so the resolution path is
readable. Real hierarchies mix abstract base classes (abc.ABC), mixins,
properties and dunder protocols such as __iter__. C3 still computes the
MRO, and super() still follows it, no matter how deep the tree.
Check yourself
Eduspheria wiki · Programming & Data Structures, Objects and I/O
0 / 5 answered
From the assignment paper
Modeled on NITJ AI-503, Assignment/Quiz
0 / 5 answered
Where next: exceptions — what to do when an operation cannot do what was asked.