Advanced Object-Oriented Programming
Real-world example: - A Car is a type of Vehicle - A Dog is a type of Animal - A Student is a type of Person
# Parent class (base class, superclass)
class Animal:
def __init__(self, name, species):
self.name = name
self.species = species
def make_sound(self):
return "Some generic animal sound"
def info(self):
return f"{self.name} is a {self.species}"
# Child class (derived class, subclass)
class Dog(Animal):
def __init__(self, name, breed):
super().__init__(name, "Canine") # Call parent constructor
self.breed = breed
def make_sound(self): # Override parent method
return "Woof!"# Create instances
generic_animal = Animal("Unknown", "Unknown species")
my_dog = Dog("Buddy", "Golden Retriever")
# Use inherited methods
print(my_dog.info()) # "Buddy is a Canine"
print(my_dog.make_sound()) # "Woof!"
# Dog inherits from Animal
print(isinstance(my_dog, Dog)) # True
print(isinstance(my_dog, Animal)) # Truesuper() Functionsuper() gives access to parent class methods:
class Vehicle:
def __init__(self, make, model, year):
self.make = make
self.model = model
self.year = year
def start(self):
return f"{self.make} {self.model} is starting"
class Car(Vehicle):
def __init__(self, make, model, year, doors):
super().__init__(make, model, year) # Call parent __init__
self.doors = doors
def start(self):
parent_start = super().start() # Call parent method
return f"{parent_start} with {self.doors} doors"Child classes can override parent methods:
class Shape:
def __init__(self, color):
self.color = color
def area(self):
return 0 # Default implementation
def describe(self):
return f"A {self.color} shape with area {self.area()}"
class Rectangle(Shape):
def __init__(self, color, width, height):
super().__init__(color)
self.width = width
self.height = height
def area(self): # Override parent method
return self.width * self.height
class Circle(Shape):
def __init__(self, color, radius):
super().__init__(color)
self.radius = radius
def area(self): # Override parent method
return 3.14159 * self.radius ** 2Python supports inheriting from multiple classes:
class Flyable:
def fly(self):
return "Flying through the air"
class Swimmable:
def swim(self):
return "Swimming in water"
class Duck(Animal, Flyable, Swimmable):
def __init__(self, name):
super().__init__(name, "Bird")
def make_sound(self):
return "Quack!"
# Duck can use methods from all parent classes
duck = Duck("Daffy")
print(duck.info()) # From Animal
print(duck.fly()) # From Flyable
print(duck.swim()) # From Swimmable
print(duck.make_sound()) # Overridden methodPython determines which method to call using 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
# Check method resolution order
print(D.__mro__)
# (<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>)
d = D()
print(d.method()) # "B" (follows MRO)Use ABC to define interfaces:
from abc import ABC, abstractmethod
class Shape(ABC):
def __init__(self, color):
self.color = color
@abstractmethod
def area(self):
pass # Must be implemented by subclasses
@abstractmethod
def perimeter(self):
pass # Must be implemented by subclasses
def describe(self):
return f"A {self.color} shape"
# Cannot instantiate abstract class
# shape = Shape("red") # TypeError
class Rectangle(Shape):
def __init__(self, color, width, height):
super().__init__(color)
self.width = width
self.height = height
def area(self):
return self.width * self.height
def perimeter(self):
return 2 * (self.width + self.height)Same interface, different implementations:
def calculate_total_area(shapes):
total = 0
for shape in shapes:
total += shape.area() # Calls appropriate area() method
return total
# Different shapes, same interface
shapes = [
Rectangle("red", 5, 4),
Circle("blue", 3),
Rectangle("green", 2, 8)
]
print(f"Total area: {calculate_total_area(shapes)}")“If it walks like a duck and quacks like a duck, it’s a duck”
class Dog:
def make_sound(self):
return "Woof!"
class Cat:
def make_sound(self):
return "Meow!"
class Robot:
def make_sound(self):
return "Beep!"
def make_all_sounds(animals):
for animal in animals:
print(animal.make_sound()) # Works with any object with make_sound()
# No inheritance needed - just same interface
creatures = [Dog(), Cat(), Robot()]
make_all_sounds(creatures)class Employee:
def __init__(self, name, employee_id, salary):
self.name = name
self.employee_id = employee_id
self.salary = salary
def get_info(self):
return f"Employee: {self.name} (ID: {self.employee_id})"
def calculate_pay(self):
return self.salary
class Manager(Employee):
def __init__(self, name, employee_id, salary, team_size):
super().__init__(name, employee_id, salary)
self.team_size = team_size
self.bonus_rate = 0.1
def calculate_pay(self):
base_pay = super().calculate_pay()
bonus = base_pay * self.bonus_rate * (self.team_size / 10)
return base_pay + bonus
def get_info(self):
base_info = super().get_info()
return f"{base_info}, Manager of {self.team_size} people"
class Developer(Employee):
def __init__(self, name, employee_id, salary, programming_languages):
super().__init__(name, employee_id, salary)
self.programming_languages = programming_languages
def get_info(self):
base_info = super().get_info()
languages = ", ".join(self.programming_languages)
return f"{base_info}, Developer ({languages})"# Create different types of employees
manager = Manager("Alice Johnson", "M001", 80000, 5)
developer = Developer("Bob Smith", "D001", 70000, ["Python", "JavaScript"])
employee = Employee("Charlie Brown", "E001", 50000)
# Polymorphism in action
employees = [manager, developer, employee]
for emp in employees:
print(emp.get_info())
print(f"Pay: ${emp.calculate_pay():,.2f}")
print("-" * 40)Good hierarchy design principles: - Is-a relationship: Use inheritance for “is-a” relationships - Liskov Substitution Principle: Subclasses should be substitutable for parent classes - Single Responsibility: Each class should have one reason to change - Open/Closed Principle: Open for extension, closed for modification
When to use composition instead of inheritance:
# Composition: "has-a" relationship
class Engine:
def start(self):
return "Engine started"
class Car:
def __init__(self, make, model):
self.make = make
self.model = model
self.engine = Engine() # Car HAS an Engine
def start(self):
return f"{self.make} {self.model}: {self.engine.start()}"
# Inheritance: "is-a" relationship
class Vehicle:
def move(self):
return "Moving"
class Car(Vehicle): # Car IS a Vehicle
def move(self):
return "Driving on roads"Challenge 1: Animal Hierarchy
class Animal:
def __init__(self, name):
self.name = name
def speak(self):
pass
class Dog(Animal):
# Implement speak method
pass
class Cat(Animal):
# Implement speak method
pass
# Test polymorphism
animals = [Dog("Buddy"), Cat("Whiskers")]
for animal in animals:
print(f"{animal.name}: {animal.speak()}")Animal Hierarchy:
Shape Calculator:
super() appropriately: Call parent methods when needed1. Deep inheritance hierarchies:
2. Multiple inheritance confusion:
Good use cases: - Clear “is-a” relationships - Shared behavior among related classes - Need for polymorphism - Framework extension points
Consider alternatives: - Composition: For “has-a” relationships - Mixins: For adding functionality - Protocols: For duck typing (Python 3.8+) - Functions: For simple code reuse
super() to access parent class functionalityComing up next: - Advanced Python features (decorators, generators) - Error handling best practices - File I/O and data processing
Practice more: - Design a game with character inheritance - Create a plugin system using abstract base classes - Build a document processing system with different file types
Python Tutorial