Skip to main content

Module 11 - OOP Advanced (Inheritance, Polymorphism, Encapsulation)

This module covers advanced OOP concepts including inheritance, polymorphism, encapsulation, multiple inheritance, and method resolution order (MRO).


1. Inheritance

1.1 Basic Inheritance

# Base (parent) class
class Animal:
def __init__(self, name):
self.name = name

def speak(self):
return "Some sound"

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

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

dog = Dog("Buddy")
cat = Cat("Whiskers")

print(dog.speak()) # Buddy says Woof!
print(cat.speak()) # Whiskers says Meow!

1.2 super() Function

class Vehicle:
def __init__(self, brand, model):
self.brand = brand
self.model = model

def info(self):
return f"{self.brand} {self.model}"

class Car(Vehicle):
def __init__(self, brand, model, doors):
super().__init__(brand, model) # Call parent constructor
self.doors = doors

def info(self):
base_info = super().info() # Call parent method
return f"{base_info} with {self.doors} doors"

car = Car("Toyota", "Camry", 4)
print(car.info()) # Toyota Camry with 4 doors

1.3 Method Overriding

class Shape:
def area(self):
return 0

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

def area(self): # Override parent method
return self.width * self.height

class Circle(Shape):
def __init__(self, radius):
self.radius = radius

def area(self): # Override parent method
return 3.14159 * self.radius ** 2

shapes = [Rectangle(5, 3), Circle(4)]
for shape in shapes:
print(f"Area: {shape.area()}")
Inheritance Benefits
  • Code reusability
  • Logical hierarchy
  • Extensibility
  • Polymorphism support

2. Multiple Inheritance

2.1 Basic Multiple Inheritance

class Flyable:
def fly(self):
return "Flying"

class Swimmable:
def swim(self):
return "Swimming"

class Duck(Flyable, Swimmable):
def quack(self):
return "Quack!"

duck = Duck()
print(duck.fly()) # Flying
print(duck.swim()) # Swimming
print(duck.quack()) # Quack!

2.2 Diamond Problem and MRO

class A:
def method(self):
return "A"

class B(A):
def method(self):
return "B"

class C(A):
def method(self):
return "C"

class D(B, C):
pass

d = D()
print(d.method()) # B (follows MRO)
print(D.__mro__) # Method Resolution Order
# (<class 'D'>, <class 'B'>, <class 'C'>, <class 'A'>, <class 'object'>)

2.3 Mixins

class JSONMixin:
def to_json(self):
import json
return json.dumps(self.__dict__)

class LoggingMixin:
def log(self, message):
print(f"[{self.__class__.__name__}] {message}")

class User(JSONMixin, LoggingMixin):
def __init__(self, name, email):
self.name = name
self.email = email

user = User("Alice", "alice@example.com")
print(user.to_json()) # {"name": "Alice", "email": "alice@example.com"}
user.log("User created") # [User] User created

3. Polymorphism

3.1 Method Polymorphism

class Payment:
def process(self, amount):
raise NotImplementedError("Subclass must implement")

class CreditCard(Payment):
def process(self, amount):
return f"Processing ${amount} via Credit Card"

class PayPal(Payment):
def process(self, amount):
return f"Processing ${amount} via PayPal"

class Bitcoin(Payment):
def process(self, amount):
return f"Processing ${amount} via Bitcoin"

def make_payment(payment_method, amount):
print(payment_method.process(amount))

# Polymorphism in action
make_payment(CreditCard(), 100)
make_payment(PayPal(), 50)
make_payment(Bitcoin(), 200)

3.2 Duck Typing

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

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

class Robot:
def speak(self):
return "Beep boop!"

def animal_sound(animal):
# Duck typing: If it has speak(), call it
print(animal.speak())

animal_sound(Dog()) # Woof!
animal_sound(Cat()) # Meow!
animal_sound(Robot()) # Beep boop!

4. Abstract Base Classes

4.1 Creating Abstract Classes

from abc import ABC, abstractmethod

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

@abstractmethod
def perimeter(self):
pass

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

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

def perimeter(self):
return 2 * (self.width + self.height)

# shape = Shape() # TypeError: Can't instantiate abstract class
rect = Rectangle(5, 3)
print(rect.area()) # 15
print(rect.perimeter()) # 16

4.2 Abstract Properties

from abc import ABC, abstractmethod

class Vehicle(ABC):
@property
@abstractmethod
def max_speed(self):
pass

@abstractmethod
def start_engine(self):
pass

class Car(Vehicle):
@property
def max_speed(self):
return 200

def start_engine(self):
return "Engine started!"

car = Car()
print(car.max_speed) # 200
print(car.start_engine()) # Engine started!

5. Encapsulation

5.1 Access Modifiers

class BankAccount:
def __init__(self, owner, balance):
self.owner = owner # Public
self._balance = balance # Protected
self.__pin = "1234" # Private

def deposit(self, amount):
if amount > 0:
self._balance += amount

def __verify_pin(self, pin): # Private method
return pin == self.__pin

def withdraw(self, amount, pin):
if self.__verify_pin(pin) and amount <= self._balance:
self._balance -= amount
return True
return False

@property
def balance(self):
return self._balance

account = BankAccount("Alice", 1000)
account.deposit(500)
print(account.balance) # 1500
account.withdraw(200, "1234")
print(account.balance) # 1300

5.2 Property-based Encapsulation

class Employee:
def __init__(self, name, salary):
self._name = name
self._salary = salary

@property
def name(self):
return self._name

@property
def salary(self):
return self._salary

@salary.setter
def salary(self, value):
if value < 0:
raise ValueError("Salary cannot be negative")
if value < self._salary:
print("Warning: Salary decreased")
self._salary = value

@property
def annual_salary(self):
return self._salary * 12

emp = Employee("Alice", 5000)
print(emp.salary) # 5000
emp.salary = 5500 # Update
print(emp.annual_salary) # 66000

6. Composition vs Inheritance

6.1 Composition

class Engine:
def start(self):
return "Engine started"

def stop(self):
return "Engine stopped"

class Car:
def __init__(self, brand):
self.brand = brand
self.engine = Engine() # Composition

def start(self):
return f"{self.brand}: {self.engine.start()}"

def stop(self):
return f"{self.brand}: {self.engine.stop()}"

car = Car("Toyota")
print(car.start()) # Toyota: Engine started

6.2 When to Use Each

Use Inheritance WhenUse Composition When
"is-a" relationship"has-a" relationship
Strong coupling desiredFlexibility needed
Behavior sharedBehavior delegated
Example: Dog is-a AnimalExample: Car has-a Engine

7. Real-World Example

from abc import ABC, abstractmethod
from datetime import datetime

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

@abstractmethod
def calculate_salary(self):
pass

def display_info(self):
return f"{self.name} (ID: {self.employee_id})"

class FullTimeEmployee(Employee):
def __init__(self, name, employee_id, monthly_salary):
super().__init__(name, employee_id)
self.monthly_salary = monthly_salary

def calculate_salary(self):
return self.monthly_salary

class HourlyEmployee(Employee):
def __init__(self, name, employee_id, hourly_rate, hours_worked):
super().__init__(name, employee_id)
self.hourly_rate = hourly_rate
self.hours_worked = hours_worked

def calculate_salary(self):
return self.hourly_rate * self.hours_worked

class Payroll:
def __init__(self):
self.employees = []

def add_employee(self, employee):
self.employees.append(employee)

def process_payroll(self):
print("Processing Payroll...")
total = 0
for emp in self.employees:
salary = emp.calculate_salary()
total += salary
print(f"{emp.display_info()}: ${salary:.2f}")
print(f"Total Payroll: ${total:.2f}")

# Usage
payroll = Payroll()
payroll.add_employee(FullTimeEmployee("Alice", "E001", 5000))
payroll.add_employee(HourlyEmployee("Bob", "E002", 25, 160))
payroll.process_payroll()

8. Summary

ConceptDescriptionExample
InheritanceCreate class from anotherclass Dog(Animal):
super()Call parent methodsuper().__init__()
Multiple inheritanceInherit from multiple classesclass D(B, C):
PolymorphismSame interface, different implementationshape.area()
Abstract classCannot be instantiatedclass Shape(ABC):
EncapsulationHide internal detailsself.__private
CompositionHas-a relationshipself.engine = Engine()
Key Takeaways
  • Use inheritance for "is-a" relationships
  • Favor composition over inheritance when possible
  • Use abstract classes to define interfaces
  • Implement polymorphism for flexibility
  • Encapsulate internal details
  • Understand MRO for multiple inheritance

9. What's Next?

In Module 12 - Special Methods, you'll learn:

  • Dunder methods (__add__, __len__, etc.)
  • Operator overloading
  • Context managers
  • Callable objects
  • Custom containers

10. Practice Exercises

Exercise 1: Shape Hierarchy

Create an abstract Shape class and implement Circle, Rectangle, and Triangle.

Exercise 2: Employee Management

Build a complete employee management system with different employee types.

Exercise 3: Animal Kingdom

Create a multi-level inheritance hierarchy for animals.

Exercise 4: Payment System

Implement a payment processing system with multiple payment methods.

Exercise 5: Game Characters

Create a game character system using inheritance and composition.

Solutions

Try solving these exercises on your own first. Solutions will be provided in the practice section.