Abstract Classes and Interfaces
Python has no interface keyword. Abstraction is achieved through the abc module (Abstract Base Classes) or, more loosely, through typing.Protocol for structural typing.
abc.ABC and @abstractmethod
from abc import ABC, abstractmethod
class Shape(ABC):
@abstractmethod
def area(self):
pass # no implementation, subclasses MUST provide one
@abstractmethod
def perimeter(self):
pass
class Circle(Shape):
def __init__(self, radius):
self.radius = radius
def area(self):
return 3.14159 * self.radius ** 2
def perimeter(self):
return 2 * 3.14159 * self.radius
Shape() # TypeError: Can't instantiate abstract class Shape with abstract methods area, perimeter
c = Circle(5) # works fine, all abstract methods implementedEnforcement, not just convention
Unlike a plain base class that merely raises
NotImplementedErrorinside its methods (a common older-style pattern),ABCwith@abstractmethodPREVENTS instantiation of any subclass that fails to override every abstract method. The error surfaces at instantiation time, not at first-use time.
The Older Pattern (Still Seen, Weaker Guarantee)
class Shape:
def area(self):
raise NotImplementedError("Subclasses must implement area()")
class Circle(Shape):
pass
c = Circle() # instantiation succeeds! bug only surfaces when .area() is actually called
c.area() # NotImplementedError, but only discovered at call time, not at creation timeAbstract Properties
from abc import ABC, abstractmethod
class Vehicle(ABC):
@property
@abstractmethod
def max_speed(self):
pass
class Car(Vehicle):
@property
def max_speed(self):
return 200Mixing Abstract and Concrete Methods
An ABC can provide real, shared implementations alongside abstract requirements.
from abc import ABC, abstractmethod
class Employee(ABC):
def __init__(self, name):
self.name = name
def greet(self): # concrete, shared by all subclasses
return f"Hi, I'm {self.name}"
@abstractmethod
def calculate_pay(self): # abstract, each subclass must define
pass
class SalariedEmployee(Employee):
def __init__(self, name, salary):
super().__init__(name)
self.salary = salary
def calculate_pay(self):
return self.salarytyping.Protocol: Structural Typing (Duck Typing, Statically Checked)
Protocol defines an interface based purely on what methods/attributes an object HAS, with no explicit inheritance required. This matches Python’s duck-typing philosophy while still being checkable by static type checkers like mypy.
from typing import Protocol
class Drawable(Protocol):
def draw(self) -> str:
...
class Square: # note: does NOT inherit from Drawable!
def draw(self):
return "Drawing a square"
def render(item: Drawable):
print(item.draw())
render(Square()) # works, Square satisfies the Protocol structurally
ABCvsProtocolUse
ABCwhen you control the class hierarchy and want to enforce a contract via explicit inheritance, with runtime enforcement. UseProtocolfor structural/duck typing checked by static analysis tools, useful for third-party classes you cannot modify to inherit from your base class.
Why Bother with Abstraction at All
The real value
Abstract classes let you write code against a CONTRACT (“anything that is a Shape has
.area()”) rather than a specific implementation. This is the foundation of extensible, polymorphic systems: new shapes can be added later without touching any code that already works withShape.