Abstract Classes and Protocols
About 20 minutes
Why this matters
When several classes should offer the same abilities (every shape has an area), you want a clear contract: “if you are a shape, you must provide area()”. An abstract class states that contract, and Python refuses to create objects that break it.
The idea
from abc import ABC, abstractmethod
class Shape(ABC): # an abstract base class
@abstractmethod
def area(self): # subclasses MUST implement this
...
def describe(self): # normal methods are inherited as usual
return f"area {self.area():.1f}"
class Square(Shape):
def __init__(self, side):
self.side = side
def area(self):
return self.side ** 2
Shape() # TypeError: Can't instantiate abstract class Shape ...
Square(3).describe() # 'area 9.0'
A subclass that forgets an abstract method cannot be instantiated either, so the mistake shows up immediately.
Python also allows duck typing: a function that calls thing.area() works for any object with that method, related or not. When you want to describe such a contract without inheritance, typing.Protocol lets you write a class that lists the methods an object must have; tools like type checkers then verify it. Use an abstract class when you also want to share code or refuse bad subclasses, and plain duck typing when a method or two is enough.
Try it
Create class Triangle(Shape) with only __init__. What error do you get when you create one? Add area and try again.
Your turn
Write:
- An abstract class
Shapewith abstract methodsarea()andperimeter(). Square(side)andCircle(radius), both subclasses ofShapethat implement them. For a circle usemath.pi.largest(shapes): the shape with the biggest area.total_area(things): the sum of the areas of anything that has anarea()method. It must work for objects that are notShapes, too.
Recap
ABCplus@abstractmethoddefines a contract that subclasses must fulfil.- Abstract classes cannot be instantiated.
- Duck typing: depend on the methods an object has, not on its class.
Common mistakes
- Forgetting
ABC: without it@abstractmethodis not enforced. - Checking
isinstanceeverywhere when calling the method would do. - Leaving one abstract method unimplemented:
TypeError: Can't instantiate abstract class Square with abstract method perimeter.