Python isn’t like Java or C# where they have explicit interface keywords. So, how do interfaces work in Python?

Traditionally, Python uses a different approach. Instead of explicit interfaces, it relies on duck typing. That means if an object behaves like a duck (has the necessary methods), it’s treated as a duck, regardless of its type.

Duck Typing

Python relies on duck typing: If an object behaves like a duck (i.e., has the required methods/properties), it is treated as a duck. No formal interface declaration is needed.

Example:

class Duck:
    def quack(self):
        print("Quack!")

class Person:
    def quack(self):
        print("I'm pretending to be a duck!")

def make_quack(obj):
    obj.quack()

make_quack(Duck())    # Works
make_quack(Person())  # Also works

However, Python supports the concept of interfaces and similar functionality through other means. abstract base classes (ABCs) can be used as interfaces by defining methods that subclasses are required to implement. Also, Python 3.8 introduced protocols (via the typing module), which provide a more flexible approach to defining interfaces. Here’s a comparison of abstract classes as interfaces versus protocols:

Abstract Classes (ABC) as Interfaces

  1. Inherit from ABC: Abstract classes that act as interfaces typically inherit from ABC and use @abstractmethod decorators for methods that must be implemented by subclasses.
  2. Explicit Implementation: Classes inheriting from an abstract base class (ABC) must explicitly implement all abstract methods, or else they will raise a TypeError.
  3. Class Hierarchy: ABCs enforce a formal class hierarchy, requiring explicit inheritance. This is stricter than protocols, making them useful when you want more control over which classes can implement the interface.
  4. Static Type Checking: ABCs provide some type-checking benefits but are limited compared to protocols in terms of flexibility.

Example:

from abc import ABC, abstractmethod

class Drawable(ABC):
    @abstractmethod
    def draw(self):
        pass

class Circle(Drawable):
    def draw(self):
        print("Drawing a circle")

# Circle must implement draw() because Drawable is an abstract base class.

Protocols

  1. Structural Typing: Protocols enable structural (or “duck”) typing, meaning a class does not have to explicitly inherit from the protocol to be considered a “match.” Instead, it just needs to have the same methods with compatible signatures.
  2. No Explicit Inheritance Needed: A class that matches the method signatures in the protocol will automatically be compatible with the protocol type, even if it doesn’t inherit from it.
  3. More Flexible: Protocols are useful in dynamic or third-party libraries where you don’t have control over class inheritance. Any class that “quacks” like the protocol can be treated as an instance of that protocol.
  4. Static Type Checking: Protocols are primarily used for static type checking in Python, making them a better fit for scenarios where you want structural compatibility without forcing a specific class hierarchy.

Example:

from typing import Protocol

class Drawable(Protocol):
    def draw(self) -> None:
        pass

class Circle:
    def draw(self):
        print("Drawing a circle")

# Circle is compatible with Drawable protocol even without inheritance
def render_shape(shape: Drawable) -> None:
    shape.draw()

circle = Circle()
render_shape(circle)  # Works because Circle has a draw() method

Key Differences

Feature Abstract Classes (ABC) Protocols
Inheritance Required Yes, must inherit from ABC No, structural typing allows “duck typing”
Enforcement Strictly enforces method requirements Flexible, only method names and signatures must match
Static Type Checking Limited Extensive, widely used with type hints
Use Case When a formal class hierarchy is needed When flexible, interface-only compatibility is preferred
Introduced Long-standing feature in Python Introduced in Python 3.8

When to Use Which

  • Use abstract classes if you need a stricter, enforced inheritance hierarchy and want to use inheritance for code reuse or have shared default methods.
  • Use protocols when you need flexible, interface-like behavior and want to work with any class that matches the expected method signatures (without modifying the class hierarchy).

Protocols are especially beneficial for type hinting and static type checking in Python typing libraries like mypy.