
Chapter Outline
Chapter 9: Advanced Object-Oriented Programming in Python
This chapter explores advanced object-oriented programming (OOP) concepts in Python to help you build more flexible, reusable, and scalable software systems. We'll cover:
- Inheritance and polymorphism
- Duck typing and dynamic behavior
- Abstract base classes
- Composition vs inheritance
- Real-world example and test cases
9.1 Inheritance
Inheritance allows a class to reuse code from another class.
pythonclass Animal:def __init__(self, name):self.name = namedef speak(self):return "Makes a sound"class Dog(Animal):def speak(self):return f"{self.name} says Woof!"dog = Dog("Fido")print(dog.speak()) # Fido says Woof!
Note:
Doginherits the__init__method fromAnimaland overridesspeak.
9.2 Polymorphism
The term polymorphism means "many forms." In Python OOP, it refers to the ability of different object types to respond to the same method or interface.
pythonclass Dog:def speak(self):return "Woof!"class Cat:def speak(self):return "Meow!"animals = [Dog(), Cat()]for animal in animals:print(animal.speak())
Output:
bashWoof!Meow!
This is polymorphism: you call speak() on each object without knowing or caring what their specific class is.
9.3 Duck Typing
Python uses duck typing: if an object has the expected method or behavior, it's valid — regardless of its actual type.
“If it walks like a duck and quacks like a duck, it's a duck.”
pythonclass Bird:def fly(self):return "Flap flap"class Airplane:def fly(self):return "Jet sounds"def make_it_fly(flier):print(flier.fly())make_it_fly(Bird()) # Flap flapmake_it_fly(Airplane()) # Jet sounds
Python does not care about class inheritance. It just checks if the method exists at runtime.
9.4 Method Overriding
In class inheritance, subclasses can override methods from their parent class. This is another form of polymorphism.
pythonclass Shape:def area(self):raise NotImplementedError()class Circle(Shape):def __init__(self, radius):self.radius = radiusdef area(self):return 3.14 * self.radius ** 2class Square(Shape):def __init__(self, side):self.side = sidedef area(self):return self.side ** 2shapes = [Circle(3), Square(4)]for shape in shapes:print(shape.area())
Output:
bash28.2616
Each shape implements its own area() logic.
9.5 Abstract Base Classes (ABCs)
Sometimes, you want to enforce an interface across subclasses. Python’s abc module lets you do that.
pythonfrom abc import ABC, abstractmethodclass Exporter(ABC):@abstractmethoddef export(self, data):pass
Any subclass of Exporter must implement the export() method.
pythonclass JSONExporter(Exporter):def export(self, data):import jsonreturn json.dumps(data)class CSVExporter(Exporter):def export(self, data):import csvfrom io import StringIOoutput = StringIO()writer = csv.DictWriter(output, fieldnames=data[0].keys())writer.writeheader()writer.writerows(data)return output.getvalue()def save_data(exporter: Exporter, data):output = exporter.export(data)print(output)data = [{"name": "ALice", "age": 30}, {"name": "Bob", "age": 25}]save_data(JSONExporter(), data)save_data(CSVExporter(), data)
ABCs with Default Implementations
You can provide concrete methods alongside abstract ones:
pythonclass Exporter(ABC):@abstractmethoddef export(self, data):passdef validate(self, data):if not data:raise ValueError("Data must not be empty")
Subclasses inherit validate() as-is but must still implement export().
9.6 Composition vs. Inheritance
Instead of inheriting behavior, sometimes it’s better to compose classes from smaller parts.
pythonclass Engine:def start(self):return "Engine started"class Car:def __init__(self):self.engine = Engine()def start(self):return self.engine.start()car = Car()print(car.start()) # Engine started
Use composition when the relationship is "has-a"; use inheritance when the relationship is defined by "is-a".
9.7 Testing Inheritance and Polymorphism
pythonimport pytestclass Parent:def greet(self):return "Hello from Parent"class Child(Parent):def greet(self):return "Hello from Child"def test_polymorphism():obj: Parent = Child()assert obj.greet() == "Hello from Child"def test_inheritance():child = Child()assert isinstance(child, Parent)
Chapter Assignment
For this chapter we're going to extend the TaskManager example from the previous chapter and incorporate some of the new concepts we've learned here.
- Create an ABC called
BaseTaskthat can be extended by task classes such asSimpleTaskthat have the attributes of the existingTaskclass, and a newDeadlineTaskthat also have adue_dateattribute. - Update the
TaskManagerto work with a list ofBaseTask. - Create a
TaskNotifierclass to determine which one of the tasks within the list of tasks managed by theTaskManagerhave expired. - Bonus: Try adding a
RecurringTaskthat inherits fromBaseTaskbut includes recurrence logic (e.g., daily/weekly). - Hint: Use duck typing to allow third-party task types as long as they implement
__str__andmark_done.
Check your understanding
Test your knowledge of Advanced Object-Oriented Programming in Python