Explain Codes LogoExplain Codes Logo

How do I implement interfaces in Python?

python
duck-typing
interface-definition
type-checking
Anton ShumikhinbyAnton Shumikhin·Jan 10, 2025
TLDR

Implement interfaces in Python using abstract base classes (ABCs) with the @abstractmethod decorator to enforce method definitions in subclasses.

from abc import ABC, abstractmethod class IShape(ABC): @abstractmethod def draw(self): # Be sure to call this method... or else! """ Draw the shape """ class Circle(IShape): def draw(self): print("Circle drawn") # Triangle, square... step aside, it's circle time! # Instantiate and use circle = Circle() circle.draw() # Circle's debut performance

If you forget to implement the draw method in IShape(interface wannabe) within the Circle class, Python will throw a tantrum, a.k.a a TypeError. This tantrum protects your code by ensuring a consistent API across different shapes. Operator, draw the curtains please - the show has just begun.

Eco-systems of interface: multiple players, multiple roles

Duck typing/prototyping and multiple inheritance: Shape-shifting ninjas

Python classes can be sneaky like duck typing ninjas, they can mimic an interface by implementing the necessary methods without proclaiming their loyalty - "I implemented this interface". This gives way to multiple inheritance, where classes can borrow cool tricks from several interface "parents". Clever or reckless? You decide.

Python 3.8+: The shiny new typing.Protocol

Don't be fooled by the name, Python 3.8's typing.Protocol is no formality. It's an undercover base class that lets you define methods that must be implemented. This underground rebel doesn't have to inherit from the protocol. In fact, it works undercover at runtime. Code in secret, reveal in execution.

from typing import Protocol class Drawable(Protocol): def draw(self) -> None: ... class Square: def draw(self) -> None: print("Square drawn") # Square finally steps out of triangle's shadow # No inheritance, Square is a Drawable via duck typing. Quack quack. assert isinstance(Square(), Drawable) # Check completed, no ducks here

Error handling road trip: Take the highway of usability

When your code decides to take a detour and lands an error, use clear GPS navigation by using precise error messages. This will guide the fellow devs back to the highway of usability.

Go exotic: Using Python tooling for interfaces

Interface adoption agency: Zope.interface

Meet Zope.interface, the interface adoption agency. Declare a class as an interface adoptee, mark the classes or instances as implementing an interface. Zope.interface is a key player in bizarre places like component registries and lookups to uncouple the infrastructural ties between components.

from zope.interface import implementer, Interface class IWorker(Interface): def work(self): # Work, work, work! """ Performs work """ @implementer(IWorker) class Worker: def work(self): print("Working") # Workaholic alert! # More components for registry and lookup. They are multiplying!

MyPy: The neat-freak's static typing check

Keep your code neat and tidy with the mypy static type checker. It'll sweep through your code and make sure the types match the interfaces. Early cleanliness prevents "dirty" runtime surprises.

The profiler for classes: subclasses()

The __subclasses__() investigation technique, you get to know all descendants of a class across the codebase. Talk about being omniscient!

for cls in IWorker.__subclasses__(): worker_instance = cls() worker_instance.work() # No work, no pay.

Level up: Advanced interface strategies

Do you speak my version? Interface version checks

Use your interfaces to play bad cop. Implement version check methods to interrogate classes on their version compatibility.

Charm of old-school: The ‘interface’ package

If you miss the formal interface declaration a la Java, meet the interface package. Available for Python 2.7 and 3.4+, this package lets you define interfaces in that classic, structured way.

from interface import Interface, implements class IJob(Interface): def do_job(self): pass class Job(implements(IJob)): def do_job(self): print("Job done") # Finally some work done

The inheritance wizard: System components and subclassing

For those complicated scenarios like an accounting system or a flood of plugins, leverage interfaces to arrange your subclassing and inheritance mechanisms. Trust me, it feels like wielding a magic wand.