Python Series track

Week 08 · Intermediate Track

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.

Intermediate Track2 min readFree lesson

Level: Intermediate | Prerequisite: Week 7

Learning Goals

  • Use class inheritance and super()
  • Demonstrate polymorphism in code
  • Override magic methods (__str__, __eq__, __add__, etc.)
  • Distinguish between class methods and static methods

1. Inheritance

Inherit attributes and methods from a parent class.

class Animal:
    def __init__(self, name):
        self.name = name
    def speak(self):
        return "..."

class Dog(Animal):     #Animal Inheritance
    def speak(self):   #Method Overriding
        return "Woof Woof"

d = Dog("Badugi")
print(d.name, d.speak())   #Badugi Woof Woof

2. super()

Call the parent's methods.

class Puppy(Dog):
    def __init__(self, name, age):
        super().__init__(name)   #Call parent __init__
        self.age = age

3. Polymorphism

Implement the same method name differently in various classes. The caller does not care about the type.

animals = [Dog("Badugi"), Cat("Butterfly"), Cow("Spotty")]
for a in animals:
    print(a.speak())   #Woof woof / Meow / Moo

4. Magic Methods

Special methods whose names start and end with __xxx__. They are connected to Python syntax.

Method Trigger
__str__ str(x), print(x)
__repr__ Displayed in REPL, repr(x)
__len__ len(x)
__eq__ x == y
__add__ x + y
__getitem__ x[i]
__iter__ / __next__ for ... in x
class Point:
    def __init__(self, x, y):
        self.x, self.y = x, y
    def __repr__(self):
        return f"Point({self.x}, {self.y})"
    def __add__(self, other):
        return Point(self.x + other.x, self.y + other.y)
    def __eq__(self, other):
        return (self.x, self.y) == (other.x, other.y)

p = Point(1, 2) + Point(3, 4)
print(p)              # Point(4, 6)
print(p == Point(4, 6))   # True

5. classmethod / staticmethod

class Date:
    def __init__(self, y, m, d):
        self.y, self.m, self.d = y, m, d

    @classmethod
    def from_string(cls, s):
        y, m, d = map(int, s.split("-"))
        return cls(y, m, d)        #Create Instance

    @staticmethod
    def is_valid(y, m, d):
        return 1 <= m <= 12 and 1 <= d <= 31

print(Date.from_string("2026-05-09").y)
print(Date.is_valid(2026, 5, 9))

Key Examples (examples/)

File Content
01_inherit.py Inheritance and super
02_polymorphism.py Polymorphism
03_magic.py Magic methods
04_classmethod.py classmethod/staticmethod

⚠️ Common Mistakes

  1. Missing super().__init__() — Parent attributes are not initialized.
  2. Calling magic methods normally — Not p.add(q) but p + q.
  3. Only creating __eq__ and omitting __hash__ — Cannot be used as set/dict keys. __hash__ should also be defined consistently.
  4. Overusing multiple inheritance — Possible but complicated. Prefer composition if possible.

❓ FAQ

Q1. How to create an abstract class (interface)? A. Use abc.ABC and @abstractmethod.

Q2. __str__ vs __repr__ ? A. __str__ is for humans, __repr__ is for developers (debugging). Usually, defining __repr__ alone is enough.

Q3. Inheritance vs Composition? A. Use inheritance for 'is-a' relationships, and composition when 'has-a' applies (owning attributes). If unsure, use composition.

📝 Exercises (exercises/)

  • exercise1.md — Shape abstract → Rectangle, Circle
  • exercise2.md — Playlist class + magic methods
  • exercise3.md — Vector class (+, -, ==, repr)

Next Week

Week 9. Regular Expressions

Example files

The runnable files used in this lesson. Copy them into your own project and experiment.

examples/01_inherit.pyPython
class Animal:
    def __init__(self, name):
        self.name = name
    def speak(self):
        return "..."

class Dog(Animal):
    def speak(self):
        return "Woof Woof"

class Puppy(Dog):
    def __init__(self, name, age):
        super().__init__(name)
        self.age = age

d = Dog("Badugi")
p = Puppy("lump", 1)
print(d.name, d.speak())
print(p.name, p.age, p.speak())
examples/02_polymorphism.pyPython
class Animal:
    def __init__(self, name):
        self.name = name
    def speak(self):
        return "..."

class Dog(Animal):
    def speak(self): return "Woof Woof"

class Cat(Animal):
    def speak(self): return "Meow"

class Cow(Animal):
    def speak(self): return "Moo"

animals = [Dog("Badugi"), Cat("Butterfly"), Cow("Spotty")]
for a in animals:
    print(f"{a.name}: {a.speak()}")
examples/03_magic.pyPython
class Point:
    def __init__(self, x, y):
        self.x, self.y = x, y

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

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

    def __sub__(self, other):
        return Point(self.x - other.x, self.y - other.y)

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

    def __hash__(self):
        return hash((self.x, self.y))

p = Point(1, 2) + Point(3, 4)
print(p)
print(p == Point(4, 6))
print(p == Point(0, 0))

#put into set
s = {Point(1, 2), Point(1, 2), Point(3, 4)}
print(s)
examples/04_classmethod.pyPython
class Date:
    def __init__(self, y, m, d):
        self.y, self.m, self.d = y, m, d

    def __repr__(self):
        return f"Date({self.y}, {self.m}, {self.d})"

    @classmethod
    def from_string(cls, s):
        y, m, d = map(int, s.split("-"))
        return cls(y, m, d)

    @staticmethod
    def is_valid(y, m, d):
        return 1 <= m <= 12 and 1 <= d <= 31

d = Date.from_string("2026-05-09")
print(d)
print(Date.is_valid(2026, 5, 9))
print(Date.is_valid(2026, 13, 1))

Assignment

Requirements

  • Shape abstract class (defines area() and perimeter())
  • Rectangle(width, height) and Circle(radius) inheritance
  • Calculate each method accurately
  • Implement __repr__ as well

Example of use

shapes = [Rectangle(3, 4), Circle(5)]
for s in shapes:
    print(s, "→ Area:", s.area(), "Perimeter:", s.perimeter())

Assignment 2. Playlist Class

Requirements

  • Song Add/Delete, get song by index
  • len(playlist) behavior
  • for song in playlist: behavior
  • song in playlist behavior
  • Implement __repr__ as well

Example of use

p = Playlist()
p.add("Bohemian Rhapsody")
p.add("Imagine")
print(len(p))
print(p[0])
print("Imagine" in p)
for song in p:
    print(song)

Assignment 3. Vector Class (2D)

Requirements

  • Vector(x, y) constructor
  • +, - overloading
  • == (True if same coordinates)
  • __repr__ (form of Vector(1, 2))
  • magnitude() — magnitude (Pythagorean)

Example of use

v1 = Vector(3, 4)
v2 = Vector(1, 2)
print(v1 + v2)         # Vector(4, 6)
print(v1.magnitude())  # 5.0
print(v1 == Vector(3, 4))   # True
Show the reference solution

Try the assignment yourself first — comparing your attempt with a working solution is where most of the learning happens.

solution1.pyPython
import math
from abc import ABC, abstractmethod

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

    @abstractmethod
    def perimeter(self): ...

class Rectangle(Shape):
    def __init__(self, width, height):
        self.width, self.height = width, height
    def area(self):
        return self.width * self.height
    def perimeter(self):
        return 2 * (self.width + self.height)
    def __repr__(self):
        return f"Rectangle({self.width}, {self.height})"

class Circle(Shape):
    def __init__(self, radius):
        self.radius = radius
    def area(self):
        return math.pi * self.radius ** 2
    def perimeter(self):
        return 2 * math.pi * self.radius
    def __repr__(self):
        return f"Circle({self.radius})"

shapes = [Rectangle(3, 4), Circle(5)]
for s in shapes:
    print(f"{s} → Area: {s.area():.2f}, Perimeter: {s.perimeter():.2f}")
solution2.pyPython
class Playlist:
    def __init__(self):
        self._songs = []

    def add(self, song):
        self._songs.append(song)

    def remove(self, song):
        self._songs.remove(song)

    def __len__(self):
        return len(self._songs)

    def __getitem__(self, idx):
        return self._songs[idx]

    def __contains__(self, song):
        return song in self._songs

    def __iter__(self):
        return iter(self._songs)

    def __repr__(self):
        return f"Playlist({len(self._songs)} songs)"

p = Playlist()
p.add("Bohemian Rhapsody")
p.add("Imagine")
p.add("Hotel California")
print(len(p))
print(p[0])
print("Imagine" in p)
for song in p:
    print("-", song)
print(p)
solution3.pyPython
import math

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 __sub__(self, other):
        return Vector(self.x - other.x, self.y - other.y)

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

    def __hash__(self):
        return hash((self.x, self.y))

    def magnitude(self):
        return math.sqrt(self.x ** 2 + self.y ** 2)

v1 = Vector(3, 4)
v2 = Vector(1, 2)
print(v1 + v2)
print(v1 - v2)
print(v1.magnitude())
print(v1 == Vector(3, 4))

Learn this lesson with a mentor

Work through the Python Series track on your own, or join a guided cohort at our Pitampura campus and online.

Call NowEnroll Now