Inheritance
A subclass automatically gets its parent's behavior and can override specific parts — a specialized "is-a" relationship, not a shortcut for reusing unrelated code.
By the end of this lesson, you can
- Explain inheritance as a specialized "is-a" relationship, distinct from composition's "has-a"
- Write a subclass that inherits behavior from a parent class and overrides part of it
- Recognize when inheritance is misused for code reuse where composition would model the relationship better
Why it matters
A reference-only encyclopedia is still a library book — it has a
title, and it’s tracked the same way — except it can never be borrowed.
Rather than duplicating LibraryBook entirely just to change one
method, inheritance lets a new class automatically get everything
LibraryBook already does, and override only the part that’s
different.
Mental model
Inheritance models an “is-a” relationship: a ReferenceBook is a
LibraryBook — specifically, one that can never be borrowed. That’s
different from the previous lesson’s composition, which modeled “has-a”
relationships instead. A subclass automatically inherits every
attribute and method its parent defines, and can override any of
them.
class LibraryBook:
def __init__(self, title):
self.title = title
self.is_on_loan = False
def borrow(self):
if self.is_on_loan:
return False
self.is_on_loan = True
return True
class ReferenceBook(LibraryBook):
def borrow(self):
return False
ref = ReferenceBook("Encyclopedia")
print(ref.title)
print(ref.borrow())Encyclopedia
FalseReferenceBook never defines __init__ or title itself — it
inherits both from LibraryBook unchanged. It only overrides borrow,
so ref.title works exactly the way a plain LibraryBook’s would,
while ref.borrow() always refuses.
Trace it
| Call | Where it comes from | Result |
|---|---|---|
| ref.title | Inherited from LibraryBook.__init__, unchanged | "Encyclopedia" |
| ref.borrow() | Overridden by ReferenceBook — LibraryBook's version never runs | False, always |
| LibraryBook("Dune").borrow() | LibraryBook's own version | True (if not already on loan) |
Check your understanding
Or reveal the answer without checking
Answer:Woof, then ...
Dog overrides speak(), so Dog().speak() returns 'Woof'. Cat defines no methods at all (pass), so it inherits Animal's speak() unchanged, returning '...'.
Or reveal the answer without checking
Answer:No — a playlist isn't a kind of song; it has songs. Composition (Playlist holding Song instances) models this relationship correctly
This is a has-a relationship, not is-a: a Playlist contains Song instances, it isn't a specialized Song. Composition fits; inheriting from Song here would misrepresent the relationship purely to reuse code.
Practice: warm-up
Trace this program — write down what each call returns.
class Shape:
def area(self):
return 0
class Square(Shape):
def __init__(self, side):
self.side = side
def area(self):
return self.side * self.side
generic = Shape()
square = Square(4)
Calls: generic.area(), square.area().
Stuck? Reveal one hint at a time.
Hint 1
Shape defines area() itself — what does it return when nothing overrides it?
Hint 2
Square overrides area() — trace through its own version using side = 4.
Reveal the trace
Try the problem yourself before reading this. There is often more than one reasonable approach — treat this as one worked example, not the only correct answer.
generic.area() -> Shape's own area() runs -> 0
square.area() -> Square's overridden area() runs -> 4 * 4 = 16Practice: apply it
A PremiumMember class inherits from Discount purely to reuse its
apply(price) method, even though a premium member isn’t a kind of
discount:
class Discount:
def apply(self, price):
return price * 0.9
class PremiumMember(Discount):
def __init__(self, name):
self.name = name
Or reveal the answer without checking
Answer:PremiumMember "is a" Discount according to this code, which misrepresents the relationship — a premium member has a discount, or applies one, but isn't one
This reuses apply() at the cost of claiming every PremiumMember is a kind of Discount — a has-a relationship (a member has, or is eligible for, a discount) forced into an is-a shape purely for code reuse.
Modification challenge: rewrite this using composition instead —
PremiumMember should hold a Discount instance as an attribute and
delegate to its apply method, the way Member composed Account in
the previous lesson.
Summary
- Inheritance models an “is-a” relationship: a subclass automatically inherits its parent’s attributes and methods, and can override any of them.
- Overriding a method replaces the parent’s version for that subclass only — other subclasses and the parent itself keep their own behavior.
- Using inheritance purely to reuse code, when the relationship is really “has-a,” misrepresents what the subclass is and exposes more of the parent’s interface than intended.
- When in doubt, composition is usually the safer default; reach for inheritance specifically when “is a specialized kind of” genuinely describes the relationship.