Implicit Interfaces in Python

python
Author

Blaine Buxton

Published

April 21, 2024

One of the ideas that I really like from Go is implicit interfaces. It allows clients to document the exact usage of a wide object and constrain what parts to be used. The good news is that Python’s optional type system allows for implicit interfaces.

Example

The pygame.time.Clock object has many methods and let’s say we want to document and constrain that we only use the tick method. If we keep the signature the same as the implementation, there is no need to cast and type checking will make sure we use no other methods.

class SimClock(Protocol):
    def tick(self, framerate: float) -> int:
        """
        Wait for a tick to pass as defined by framerate. This is to keep the simulation running at a consistent rate.
        """
        ...

# Other code ...

clock: SimClock = SimClock, pygame.time.Clock()
while True:
    # Processing code...
    clock.tick(fps) # this is good
    # clock.get_time() <- This will fail type checking

If the method signature is not the same, then we must cast. The example below changes tick to accept an integer instead of a float and not caring about the return. Thus, being more restrictive. I feel casting is a typing code smell, so I stick with the signatures in the implementation. But, this can not be always avoided. As always there are no hard and fast rules.

class SimClock(Protocol):
    def tick(self, framerate: int) -> None:
        """
        Wait for a tick to pass as defined by framerate. This is to keep the simulation running at a consistent rate.
        """
        ...

# Other code ...

clock: SimClock = cast(SimClock, pygame.time.Clock()) # CAST!
while True:
    # Processing code...
    clock.tick(fps) # same as before

Why?

Why go through the trouble? We are tring to increase readability and help us focus on what the code is actually implementing. Reducing the surface area of an external object, helps with all of that. It means that someone new to the code doesn’t have to read through all of the methods in the library to know exactly which ones will be used in a glance. You can even add comments if none are available in the library to further reduce cognitive load.