Python interfaces: abandon ABC and switch to Protocols
Tags: python, programming, discuss, architecture
Published: 2023-01-31 21:34:56

I used a standard Python library `abc` to define interfaces for the last 10 years of my career. 
But recently, I found that relatively new Python [Protocols](https://peps.python.org/pep-0544/) are way nicer. 
People find uses for both technologies. 
But I want to convince you to completely jump ships and start using them instead of more traditional techniques.

## Python interfaces: what can you use?

Python is somewhat different from other popular languages since there are no interfaces on a language level. 
However, there are several library implementations: 
 - `abc`
 - `typing.Protocols`
 - third-party implementations like [Zope](https://zopeinterface.readthedocs.io/en/latest/README.html#declaring-interfaces)
 - custom implementations (e.g. [via metaclasses](https://realpython.com/python-interface/))
 
`abc` package is probably the most popular:

```python
from abc import ABC, abstractmethod
 
class Animal(ABC):
   @abstractmethod
   def eat(self, food) -> float:
       pass
 
   @abstractmethod
   def sleep(self, hours) -> float:
       pass
```

Next, the most frequently mentioned package seems to be [Zope](https://zopeinterface.readthedocs.io/en/latest/README.html#declaring-interfaces):

```python
from zope.interface import Interface
 
class Animal(Interface):
   def eat(self, food) -> float:
       pass
 
   def sleep(self, hours) -> float:
       pass
```

Zope is a web-related library, and its interfaces have a lot of advanced features.

Also, there are more custom packages and tutorials on the web on how to make an interface system yourself ([see example](https://realpython.com/python-interface/)).
	
Finally, there are protocols:

```python
from typing import Protocol
 
class Animal(Protocol):
   def eat(self, food) -> float:
       ...
 
   def sleep(self, hours) -> float:
       ...
```

A protocol is a formalization of Python's [“duck-typing”](https://en.wikipedia.org/wiki/Duck_typing) ideology. There are many great articles on structural typing in Python
([this](https://trm.io/2017/01/29/structural-subtyping-python), [that](https://rednafi.github.io/reflections/structural-subtyping-in-python.html)
and some [discussion](https://news.ycombinator.com/item?id=26507161)). 
Maybe protocols and interfaces are theoretically different beasts, but a protocol does the job.
I had great success replacing `abc` with `Protocols` without any downsides.

## What should you use? 

You should know that an interface system will not be localized to a small part of your codebase. 
After you choose to go with one, you’ll see it everywhere, and it's going to be hard to change in the future. 
So I would immediately dismiss any custom implementations or `Zope`. 
It’s an extra dependency you have to deal with forever: installation, versions, support, and so on. 
For example, you have to install a [Mypy plugin](https://github.com/Shoobx/mypy-zope#plugin-for-mypy-to-support-zopeinterface) to support a `zope.interface` well.
Additionally, a new developer in the team might not know this custom package, and you'll have to explain what it is and why you chose it.

The main battle will happen between `abc` and `Protocols`. 
But if you really want a `zope` vs `Protocols` battle, please [read this](https://glyph.twistedmatrix.com/2021/03/interfaces-and-protocols.html#runtime-support)
(it has a detailed analysis of the runtime benefits of `zope`).

### Static checking first

The big assumption I’m going to make is that you’re already convinced that static checking is a *must*: 
you are not going to run the code that fails `pylint/mypy`. 
Both checkers support `abc` and `Protocols` equally well. 
Also, just know that both `abc` and `Protocols` allow runtime checking, in case you need it.

### Both support explicit syntax

First, note that you still can explicitly inherit from an `abc` *and* a `Protocol`. Many arguments in
a [very good video](https://www.youtube.com/watch?v=xvb5hGLoK0A) (and comments) from [Arjan](https://www.arjancodes.com/) revolve around the misconception 
that you can’t do that with protocols. You totally can:
```python
class Giraffe(Animal):
   ...
```

So in that regard, `abc` and `protocols` could be used the same way. 
However, Protocols give you an extra degree of design freedom by default.
You can avoid explicit inheritance but still enjoy full interface checking:

```python
class Giraffe:  # no base class needed!
   def eat(self, food) -> float:
       return 0.
 
   def sleep(self, hours) -> float:
       return 1.

def feed_animal(animal: Animal):
    ... 

giraffe = Giraffe()
feed_animal(giraffe)
```

This allows you to make an interface for the code you don’t control and loosen the dependencies between modules in your codebase.
Whether to choose an implicit or explicit option is a subtle choice decided on a case-by-case basis.
A good example in favour of explicit "opt-in" for an interface is described [here](https://glyph.twistedmatrix.com/2021/03/interfaces-and-protocols.html#where-interfaces-work-best-hidden-and-complex-meanings).
Protocols do not *force* you to opt-in, but you can establish a company-wide rule to explicitly inherit from any protocol.

`abc` also support implicit interfaces through the concept of ["virtual subclasses"](https://docs.python.org/3/library/abc.html#abc.ABCMeta.register).
But you have to call `register` for every implementation:
```python
class Giraffe:  # no base class needed!
   def eat(self, food) -> float:
       return 0.

class Animal(ABC):
    ...

Animal.register(Giraffe)  # achieves the same as implicit Protocol
```

`Procotol` supports implicit and explicit variants without extra syntax and works with `mypy`.
Also, [`mypy` does not support `register`](https://github.com/python/mypy/issues/2922) as of the end of 2022.
I'm not sure if we can fully count that in favour of `abc`.

Protocols allow you to define an interface for a *function* (not only a class) - see [callback protocols](https://mypy.readthedocs.io/en/stable/protocols.html#callback-protocols).
It is a very cool feature that is worthy of a separate post.
	
### Both support default methods :(
Unfortunately, there is a big downside to both `abc` and `Protocols`.
In the real world, many people work in a single codebase. 
Abstract base classes sometimes tend to acquire default method implementations.
This is what it might look like:

```python
class Animal(Protocol):  # the same holds for Animal(ABC):
   def eat(self, food) -> float:
       ...  # this is still abstract
 
   def sleep(self, hours) -> float:
       return 3.
```

In that case, they stop being “abstract” and become just base classes.
Python and static checkers do not catch that.
A software design with inheritance is not really the same as a design with interfaces. 
I would love Python to separate them on a language level, but it is unlikely to happen. 
Implicit protocols have an advantage here. 
They allow you to avoid messy inheritance altogether.

### Protocols are shorter

Last but not least, you can count the number of lines of code you need to define an interface.
With `abc`, you must have an `abstractmethod` decorator for every method.
But with Protocols without runtime checking, you don't have to use any decorators at all.
So here, Protocols win hands down.

## Conclusion

Let’s add up the scores:

| Capability            | ABC    | Protocols    |
| :---                  | :----: |  :---:        |
| Runtime checking      | 1      | 1            |
| Static checking       | 1      | 1            |
| Explicit interface with inheritance | 1 | 1   |
| Implicit interface without inheritance (`abc` requires `register`) | 0.5 | 1   |
| Ability to have default method implementation | -1 | -1 |
| Callback interface    | 0 | 1 |
| Number of lines | -1 | 0 |
| | | |
| Total | 1.5 | **4** |

Hopefully, I’m not missing anything huge in this analysis. Thank you for reading!
Looking at the results, team **"Protocols"** wins, and you probably should just start using it! 

Thank you for reading! You can find me on [LinkedIn](https://www.linkedin.com/in/oleg-sinavski/) or [Twitter](https://twitter.com/SinavskiOleg).

_Originally published at [https://sinavski.com](https://sinavski.com)._