Go like Interfaces in Python
Introduction
I’ve been using Go to build scalable high throughput, low(ish) latency systems for quite a while now. I was recently messing around with Python and I realized how much I missed dynamic types.
“interfaces” in python?
In Python, any method can be called on any object - there’s no compile-time checking. While this flexibility is nice, I want to make explicit type assertions about my code’s behavior at compile time. This is extremely valuable, especially in large production codebases that multiple engineers potentially across multiple timezones are working on.
Let’s look at a practical example. Say we have a function that needs to read data from either stdin or a file. In Go, there is the io.Reader
interface - any type that has a Read
method automatically implements it.
My first attempt at emulating Go’s compile-time interface checks in Python was to use abstract base classes and methods1.
from abc import ABC, abstractmethod
class Reader(ABC):
@abstractmethod
def read() -> bytes:
pass
class FileReader(Reader):
def __init__(self, filename: str):
self.file = open(filename, 'rb')
def read(self) -> bytes:
return self.file.read()
def __del__(self):
self.file.close()
class StdinReader(Reader):
def read(self) -> bytes:
return sys.stdin.buffer.read()
I’ve always had mixed feelings about inheritance. While it has its uses, I generally try to avoid it. Go has a really nice writeup about this - https://go.dev/doc/faq#inheritance - that explains why they chose composition over inheritance.
My main issue with this approach is that it requires explicit subclassing in Python. This means that methods other than the method are also inherited. This approach relies on PR reviews ensuring that the Reader class doesn’t get bloated with unrelated properties/methods. While I am a huge proponent of PR reviews, I think a better design would prevent something like this happening in the first place.
“interfaces” in python
After some digging, I found out about Protocols in python! These were exactly what I was looking for. They let you define structural types without inheritance, just like Go interfaces.
Here’s how you can implement something similar to Go’s io.Reader
in Python using the SupportsRead
protocol:
from typing import Protocol
class SupportsRead(Protocol):
def read(self) -> bytes:
...
class FileReader(SupportsRead):
def __init__(self, filename: str):
self.file = open(filename, 'rb')
def read(self) -> bytes:
return self.file.read()
def __del__(self):
self.file.close()
class StdinReader(SupportsRead):
def read(self) -> bytes:
return sys.stdin.buffer.read()
clear is better than clever
One of the Go proverbs that has resonated with me beyond the Go language is “clear is better than clever”.
While I do have some minor gripes about Go’s type system - like how they handled enums, the late addition of generics, and optional types. In practice, these have been more like slight annoyances than major issues. I’ve always found workable solutions for production systems, and the overall simplicity of Go’s type system, especially its interface design, has made it a pleasure to work with.
Python’s Protocol classes bring some of that Go-like interface design to Python, letting me write more “type-safe” code while keeping Python’s flexibility.
Notes
I know it is not a compile time check in python but rather a pre-runtime check thanks to some Python type hints and linters ↩︎