Python 3 — OOP: Inheritance & Dunder Methods
Inheritance is one of the most powerful features of OOP. It allows a class to inherit the attributes and methods of another class, promoting code reuse and building logical hierarchies. Dunder (double underscore) methods let you customize how Python's built-in operations behave for your custom classes.
1 Basic Inheritance
Python 3 — Inheritance▶ Run Code
# Parent class (Base class)
class Animal:
def __init__(self, name, species):
self.name = name
self.species = species
self.is_alive = True
def eat(self):
print(f"{self.name} is eating.")
def sleep(self):
print(f"{self.name} is sleeping. Zzzz...")
def __str__(self):
return f"{self.name} ({self.species})"
# Child class inherits from Animal
class Dog(Animal):
def __init__(self, name, breed):
super().__init__(name, "Canis familiaris") # Call parent's __init__
self.breed = breed
# New method — only for Dog
def fetch(self, item="ball"):
print(f"{self.name} fetches the {item}!")
# Overriding parent method
def __str__(self):
return f"{self.name} the {self.breed}"
class Cat(Animal):
def __init__(self, name, indoor=True):
super().__init__(name, "Felis catus")
self.indoor = indoor
def purr(self):
print(f"{self.name}: Purrrrr...")
# Creating instances
dog = Dog("Buddy", "Golden Retriever")
cat = Cat("Whiskers")
dog.eat() # Inherited from Animal
dog.fetch() # Dog-specific
cat.sleep() # Inherited from Animal
cat.purr() # Cat-specific
print(dog) # Uses Dog's __str__
2 Method Overriding & super()
Python 3 — Method Override▶ Run Code
class Shape:
def __init__(self, color="white"):
self.color = color
def area(self):
return 0 # Default implementation
def describe(self):
return f"A {self.color} {type(self).__name__} with area {self.area():.2f}"
class Rectangle(Shape):
def __init__(self, width, height, color="blue"):
super().__init__(color)
self.width = width
self.height = height
def area(self): # Override parent's area()
return self.width * self.height
def perimeter(self):
return 2 * (self.width + self.height)
class Circle(Shape):
def __init__(self, radius, color="red"):
super().__init__(color)
self.radius = radius
def area(self): # Override parent's area()
import math
return math.pi * self.radius ** 2
r = Rectangle(5, 10)
c = Circle(7)
print(r.describe()) # A blue Rectangle with area 50.00
print(c.describe()) # A red Circle with area 153.94
3 Polymorphism
Polymorphism means different classes can be used interchangeably as long as they share the same interface (methods):
Python 3 — Polymorphism▶ Run Code
class Dog:
def speak(self):
return "Woof!"
class Cat:
def speak(self):
return "Meow!"
class Duck:
def speak(self):
return "Quack!"
# Polymorphism in action — same interface, different behavior
animals = [Dog(), Cat(), Duck(), Dog(), Cat()]
for animal in animals:
# Each calls its own speak() — Python figures it out!
print(f"{type(animal).__name__}: {animal.speak()}")
# Function that works with any animal
def make_noise(animal):
print(f"The {type(animal).__name__} says: {animal.speak()}")
make_noise(Dog())
make_noise(Cat())
4 Multiple Inheritance & MRO
Python 3 — Multiple Inheritance▶ Run Code
class Flyable:
def fly(self):
return f"{self.__class__.__name__} is flying!"
class Swimmable:
def swim(self):
return f"{self.__class__.__name__} is swimming!"
class Walkable:
def walk(self):
return f"{self.__class__.__name__} is walking!"
class Duck(Flyable, Swimmable, Walkable):
def quack(self):
return "Quack!"
class FlyingFish(Flyable, Swimmable):
pass
donald = Duck()
print(donald.fly()) # Duck is flying!
print(donald.swim()) # Duck is swimming!
print(donald.walk()) # Duck is walking!
print(donald.quack()) # Quack!
# MRO — Method Resolution Order
print(Duck.__mro__) # Order Python searches for methods
5 Dunder (Magic) Methods
Dunder methods let your classes work with Python's built-in operators and functions:
| Method | Triggered by |
|---|---|
__str__ | print(obj), str(obj) |
__repr__ | repr(obj), REPL display |
__len__ | len(obj) |
__add__ | obj1 + obj2 |
__eq__ | obj1 == obj2 |
__lt__ | obj1 < obj2 |
__getitem__ | obj[key] |
__contains__ | item in obj |
__iter__ | for item in obj |
Python 3 — Dunder Methods▶ Run Code
class Vector:
"""2D Vector class with operator overloading."""
def __init__(self, x, y):
self.x = x
self.y = y
def __str__(self):
return f"Vector({self.x}, {self.y})"
def __repr__(self):
return f"Vector(x={self.x}, y={self.y})"
def __add__(self, other): # v1 + v2
return Vector(self.x + other.x, self.y + other.y)
def __sub__(self, other): # v1 - v2
return Vector(self.x - other.x, self.y - other.y)
def __mul__(self, scalar): # v * number
return Vector(self.x * scalar, self.y * scalar)
def __eq__(self, other): # v1 == v2
return self.x == other.x and self.y == other.y
def __abs__(self): # abs(v) — magnitude
return (self.x**2 + self.y**2) ** 0.5
v1 = Vector(3, 4)
v2 = Vector(1, 2)
print(v1 + v2) # Vector(4, 6)
print(v1 - v2) # Vector(2, 2)
print(v1 * 3) # Vector(9, 12)
print(v1 == v2) # False
print(abs(v1)) # 5.0 (Pythagorean theorem)
6 Abstract Classes
Python 3 — Abstract Classes▶ Run Code
from abc import ABC, abstractmethod
class PaymentProcessor(ABC):
"""Abstract base class for payment processors."""
@abstractmethod
def process_payment(self, amount):
"""All subclasses MUST implement this."""
pass
@abstractmethod
def refund(self, amount, transaction_id):
pass
def get_fee(self, amount):
"""Concrete method — shared by all processors."""
return amount * 0.02
class StripeProcessor(PaymentProcessor):
def process_payment(self, amount):
fee = self.get_fee(amount)
print(f"Stripe: Processing ₹{amount} (fee: ₹{fee:.2f})")
return "TXN_STRIPE_001"
def refund(self, amount, txn_id):
print(f"Stripe: Refunding ₹{amount} for {txn_id}")
# Can't instantiate abstract class:
# p = PaymentProcessor() # TypeError!
stripe = StripeProcessor()
txn = stripe.process_payment(1000)
stripe.refund(200, txn)
7 isinstance() & issubclass()
Python 3 — isinstance & issubclass▶ Run Code
class Animal: pass
class Dog(Animal): pass
class Cat(Animal): pass
d = Dog()
c = Cat()
# isinstance — checks object type
print(isinstance(d, Dog)) # True
print(isinstance(d, Animal)) # True (Dog IS an Animal)
print(isinstance(d, Cat)) # False
# issubclass — checks class hierarchy
print(issubclass(Dog, Animal)) # True
print(issubclass(Cat, Dog)) # False
# Practical use in functions
def make_sound(animal):
if isinstance(animal, Dog):
print("Woof!")
elif isinstance(animal, Cat):
print("Meow!")
else:
print("...")
8 Coding Challenge
Build a shape hierarchy with operator overloading:
- Abstract base
Shapewith abstractarea()andperimeter() - Concrete classes:
Rectangle,Circle,Triangle - Each class should implement
__str__,__eq__(same area), and__lt__(smaller area) - Create a list of mixed shapes, sort them by area using
sorted() - Use
isinstance()to count how many of each type exist - Find the shape with the largest area using
max()