← Back to Python series
⚙️
Intermediate
Inheritance · super() · Polymorphism · __str__ · __eq__ · __len__

Week 8 — OOP Part 2: Inheritance & Dunder Methods

Extend classes with inheritance, call parent methods with super(), implement polymorphism, and customize object behavior with dunder (magic) methods.

inheritancesuperpolymorphismdunderABC
Duration
3 hours
Level
📊 Intermediate
Prerequisite
🎯 Week 7
OUTCOME
Build a shape hierarchy with area/perimeter polymorphism and rich comparison

What you'll learn

  • 1Create a child class that inherits from a parent
  • 2Call parent __init__ with super()
  • 3Override methods for polymorphism
  • 4Define abstract base classes with ABC
  • 5Implement __str__, __repr__, __eq__, __lt__, __len__, __add__

1. Inheritance

python
class Animal:
    def __init__(self, name):
        self.name = name

    def speak(self):
        raise NotImplementedError

class Dog(Animal):
    def speak(self):
        return f"{self.name}: Woof!"

class Cat(Animal):
    def speak(self):
        return f"{self.name}: Meow!"

for a in [Dog("Fido"), Cat("Whiskers")]:
    print(a.speak())

2. Abstract Base Classes

python
from abc import ABC, abstractmethod

class Shape(ABC):
    @abstractmethod
    def area(self) -> float: ...

    @abstractmethod
    def perimeter(self) -> float: ...

    def __str__(self):
        return f"{self.__class__.__name__}: area={self.area():.2f}"

class Circle(Shape):
    def __init__(self, r): self.r = r
    def area(self): return 3.14159 * self.r ** 2
    def perimeter(self): return 2 * 3.14159 * self.r

class Rectangle(Shape):
    def __init__(self, w, h): self.w, self.h = w, h
    def area(self): return self.w * self.h
    def perimeter(self): return 2 * (self.w + self.h)

shapes = [Circle(5), Rectangle(3, 4)]
for s in shapes: print(s)

3. Dunder Methods

python
class Vector:
    def __init__(self, x, y):
        self.x, self.y = x, y

    def __repr__(self):
        return f"Vector({self.x}, {self.y})"

    def __add__(self, other):
        return Vector(self.x + other.x, self.y + other.y)

    def __eq__(self, other):
        return self.x == other.x and self.y == other.y

    def __abs__(self):
        return (self.x**2 + self.y**2)**0.5

v1 = Vector(1, 2)
v2 = Vector(3, 4)
print(v1 + v2)   # Vector(4, 6)
print(abs(v2))   # 5.0

4. Common Mistakes

  1. Forgetting to call super().__init__() in the child — parent attributes won't be initialized.
  2. Overriding __eq__ without __hash__ makes the object unhashable (can't be used in sets/dicts).
  3. Multiple inheritance with diamond problem — Python's MRO (C3 linearization) resolves this, but be cautious.

💻 Examples

Run these examples and check the output yourself.

01_shapes.pyShape hierarchy with polymorphic area/perimeter
CODE
import math
from abc import ABC, abstractmethod

class Shape(ABC):
    @abstractmethod
    def area(self): ...
    @abstractmethod
    def perimeter(self): ...
    def __str__(self):
        return f"{type(self).__name__}(area={self.area():.2f}, perim={self.perimeter():.2f})"

class Circle(Shape):
    def __init__(self, r): self.r = r
    def area(self): return math.pi * self.r**2
    def perimeter(self): return 2 * math.pi * self.r

class Rect(Shape):
    def __init__(self, w, h): self.w, self.h = w, h
    def area(self): return self.w * self.h
    def perimeter(self): return 2*(self.w+self.h)

for s in [Circle(5), Rect(3, 4), Circle(1)]:
    print(s)
▶ Output
Circle(area=78.54, perim=31.42)
Rect(area=12.00, perim=14.00)
Circle(area=3.14, perim=6.28)

📝 Exercises

Try them yourself first, then open the solution to compare.

Exercise 1

Fraction class

Goal: Implement a Fraction class with arithmetic operators.

Requirements
  • __init__(numerator, denominator)
  • Reduce to lowest terms using gcd
  • __add__, __sub__, __mul__, __truediv__
  • __str__ → '3/4', __repr__ → Fraction(3, 4)
  • __eq__ for comparison
Toggle solution
SOLUTION
from math import gcd

class Fraction:
    def __init__(self, n, d):
        if d == 0: raise ZeroDivisionError
        g = gcd(abs(n), abs(d))
        self.n, self.d = n//g * (1 if d>0 else -1), abs(d)//g

    def __repr__(self): return f"Fraction({self.n}, {self.d})"
    def __str__(self): return f"{self.n}/{self.d}"
    def __add__(self, o): return Fraction(self.n*o.d + o.n*self.d, self.d*o.d)
    def __sub__(self, o): return Fraction(self.n*o.d - o.n*self.d, self.d*o.d)
    def __mul__(self, o): return Fraction(self.n*o.n, self.d*o.d)
    def __truediv__(self, o): return Fraction(self.n*o.d, self.d*o.n)
    def __eq__(self, o): return self.n == o.n and self.d == o.d

a, b = Fraction(1,2), Fraction(1,3)
print(a+b, a-b, a*b, a/b)
▶ Output
5/6 1/6 1/6 3/2
Example code / lecture materials

All lecture materials and example code are openly available on GitHub.

View on GitHub ↗