Day 14 / 25 Inheritance & Polymorphism 0/0 exercises Exercises ↓

AmouAI Hub/Courses/Programming Fundamentals/Day 14

Week 3 · Algorithms & Object-Oriented Python · Day 14

Inheritance & Polymorphism

One call, different behaviour. Build the hierarchy in Python first — Week 4 rebuilds the same design in Java and the contrast does the teaching.

Study time
4 hours
Reading
Think Python, Ch. 18
Focus
super() · duck typing · ABCs

By the end of today you can

  1. Write a subclass and override a method
  2. Call the parent's version with super(), and say why you usually should
  3. Trace attribute lookup up the inheritance chain
  4. Explain polymorphism without using the word "polymorphism"
  5. Say what duck typing is, and what it costs
  6. Use an abstract base class to make a contract explicit
  7. Recognise a hierarchy that has gone too deep

Today's videos

Watch each video, then work the matching sections below. Watching alone will not do it.

14A
Overriding & super() (120 min)
Writing a subclass · overriding a method · super() and the bug when you forget it · attribute lookup walking the chain · a describe() defined once that works for every subclass.
14B
Polymorphism, Duck Typing & ABCs (120 min)
The if-chain that polymorphism removes · adding a class without touching the loop · duck typing, and what it costs · abstract base classes failing at construction rather than at call time · how deep a hierarchy should go.

1Overriding and super()

Inherit everything, then change the part that differs.

shapes.py
class Shape:
    """A shape with a name. Area is undefined here."""

    def __init__(self, name):
        self.name = name

    def area(self):
        raise NotImplementedError("subclasses must define area")

    def describe(self):
        return f"{self.name} with area {self.area():.2f}"


class Circle(Shape):
    def __init__(self, radius):
        super().__init__("circle")     # let Shape do its part
        self.radius = radius

    def area(self):                    # override
        return 3.14159 * self.radius ** 2


class Rectangle(Shape):
    def __init__(self, width, height):
        super().__init__("rectangle")
        self.width = width
        self.height = height

    def area(self):
        return self.width * self.height


print(Circle(2).describe())
print(Rectangle(3, 4).describe())
circle with area 12.57 rectangle with area 12.00
Forgetting super().__init__ is the classic subclass bug

Leave it out and self.name never gets set, so describe() fails with AttributeError: 'Circle' object has no attribute 'name' — and the traceback points at describe, not at the constructor where the mistake actually is.

Look closely at describe(). It is defined once, on Shape, and it calls self.area() — which does not exist on Shape in any useful form. Yet it works for every subclass. That is the whole idea, and section 2 names it.

Attribute lookup walks up the chain and stops at the first match. Square defines no area, so Python finds Rectangle's. Nothing defines describe except Shape, so every object in the tree shares that one implementation.

2Polymorphism

One call, different behaviour — and no if-statement anywhere.

poly.py
shapes = [Circle(1), Rectangle(2, 3), Circle(0.5)]

for shape in shapes:
    print(shape.describe())

total = sum(s.area() for s in shapes)
print(f"total area {total:.2f}")
circle with area 3.14 rectangle with area 6.00 circle with area 0.79 total area 9.93
Polymorphism, without the word

The loop does not know or care what kind of shape it has. It calls describe() and the object decides what that means. Adding a Triangle class requires zero changes to this loop — and that is the entire practical payoff of inheritance.

Duck typing

If it walks like a duck and quacks like a duck, treat it as a duck. Python never checks the type — it just calls the method and sees what happens.

duck.py
class Invoice:
    """Not a Shape. Does not inherit from anything."""

    def describe(self):
        return "invoice #4471"


things = [Circle(1), Rectangle(2, 3), Invoice()]

for t in things:
    print(t.describe())
circle with area 3.14 rectangle with area 6.00 invoice #4471
Duck typing gives youAnd costs you
Flexibility — no shared base class neededNo compile-time guarantee the method exists
Easy fakes and test doublesAttributeError at runtime, possibly in production
Less ceremonyThe contract is implicit — nothing states it
Hold this thought until Day 19

Java will not let you do this. It demands the contract be written down as an interface before the code compiles. Which is better depends entirely on the project — and having felt both is the reason this course teaches two languages.

3Abstract base classes

Writing the contract down, in Python.

raise NotImplementedError in the base class works, but it fails late — only when someone actually calls the missing method. An abstract base class fails at the moment you try to create the object.

abc_demo.py
from abc import ABC, abstractmethod


class Shape(ABC):
    def __init__(self, name):
        self.name = name

    @abstractmethod
    def area(self):
        """Every shape must define this."""

    def describe(self):
        return f"{self.name} with area {self.area():.2f}"


class Blob(Shape):
    def __init__(self):
        super().__init__("blob")
    # area() deliberately missing


b = Blob()
TypeError: Can't instantiate abstract class Blob with abstract method area
The wording changed in Python 3.12

On 3.9-3.11 you get the message above. On 3.12 and later it reads Can't instantiate abstract class Blob without an implementation for abstract method 'area'. Same error, same cause — do not be thrown if your version phrases it differently.

The error arrives at construction, names the class and names the missing method. Compare that with an AttributeError deep inside a report-generation function three weeks later.

ApproachFails whenUse when
Nothing — pure duck typingThe method is calledSmall scripts, quick work
raise NotImplementedErrorThe method is calledYou want a base class but not the import
ABC + @abstractmethodThe object is createdThe contract matters to other people

How deep should a hierarchy go?

Two levels is usually enough. Four is a smell.

Every level adds a place to look when you are trying to work out where a method actually comes from. If you find yourself writing Animal → Mammal → Canine → Dog → WorkingDog, ask what behaviour genuinely differs at each level — and whether a held object would say it more clearly than a parent class.

Today's work: build a small shape hierarchy with an ABC. Keep the file — on Day 19 you will rebuild this exact design in Java, and the differences are the lesson.

>_Python playground

A real Python interpreter running inside your browser. Nothing is installed, nothing is uploaded, nothing can break.

scratch.pypython not loaded
Values for input(), comma separated →
Output appears here. The first run takes a few seconds while Python loads.

Exercise set

Checked automatically the moment you submit. Work top to bottom — each one assumes the last. Your answers are saved in this browser.

All Warm-up Core Challenge Reset day

Day 15 — Exceptions, Testing & Mini-Project 3

Failing well, and proving you did. Plus Mini-Project 3.

Continue →