Python provides two ways to define interfaces more formally than plain duck typing: abstract base classes (nominal, inheritance-based) and Protocols (structural, inheritance-free).
from abc import ABC, abstractmethod
class Shape(ABC): # inheriting from ABC marks this as abstract
@abstractmethod
def area(self): # subclasses MUST implement this
pass
class Circle(Shape):
def __init__(self, radius):
self.radius = radius
def area(self):
return 3.14159 * self.radius ** 2
# shape = Shape() # ❌ TypeError — can't instantiate an abstract class directly
circle = Circle(5) # ✅ fine — Circle implements the required area()
from typing import Protocol
class Drawable(Protocol): # structural — no inheritance needed to "implement" this
def draw(self) -> None: ...
class Square: # doesn't inherit from Drawable at all...
def draw(self) -> None:
print("Drawing a square")
def render(obj: Drawable) -> None: # ...but still satisfies the Protocol, checked by type checkers
obj.draw()
render(Square()) # works — Square has a matching draw() method
An abstract base class (ABC from abc) is Python's closest equivalent to a Java/C++ interface: marking a class as abstract with ABC and its methods with @abstractmethod prevents instantiating the base class directly and forces every concrete subclass to implement those methods, enforced with a real TypeError at instantiation time if they don't.
A Protocol (typing.Protocol, Python 3.8+) takes the opposite approach: structural typing, where a class satisfies the protocol simply by having the right methods with the right signatures — no explicit inheritance required at all, matching Python's duck-typing spirit while still giving type checkers (like mypy) something concrete to verify. Reach for an ABC when you want to enforce a contract at runtime with actual errors; reach for a Protocol when you want static-analysis-time checking without imposing an inheritance relationship on unrelated classes.