Implements
:snake: Pythonic interfaces using decorators
Install / Use
/learn @ksindi/ImplementsREADME
Implements
.. image:: https://travis-ci.org/ksindi/implements.svg?branch=master :target: https://travis-ci.org/ksindi/ksindi/implements :alt: Build Status .. image:: https://img.shields.io/pypi/v/implements.svg :target: https://pypi.python.org/pypi/implements :alt: PyPI Version
Pythonic interfaces using decorators
Decorate your implementation class with @implements(<InterfaceClass>).
That's it! implements will ensure that your implementation satisfies
attributes, methods and their signatures as defined in your interface.
Moreover, interfaces are enforced via composition. Implementations don't inherit interfaces. Your MROs remain untouched and interfaces are evaluated early during import instead of class instantiation.
Install
Implements is available on PyPI and can be installed with pip <https://pip.pypa.io>_::
pip install implements
Note Python 3.6+ is required as it relies on new features of inspect module.
Advantages
-
Favor composition over inheritance <https://en.wikipedia.org/wiki/Composition_over_inheritance>_. -
Inheriting from multiple classes can be problematic, especially when the superclasses have the same method name but different signatures. Implements will throw a descriptive error if that happens to ensure integrity of contracts.
-
The decorators are evaluated at import time. Any errors will be raised then and not when an object is instantiated or a method is called.
-
It's cleaner. Using decorators makes it clear we want shared behavior. Also, arguments are not allowed to be renamed.
Usage
With implements, implementation classes and interface classes must have their own independent class hierarchies. Unlike common patterns, the implementation class must not inherit from an interface class. From version 0.3.0 and onwards, this condition is checked automatically and an error is raised on a violation.
.. code-block:: python
from implements import Interface, implements
class Duck:
def __init__(self, age):
self.age = age
class Flyable(Interface):
@staticmethod
def migrate(direction):
pass
def fly(self) -> str:
pass
class Quackable(Interface):
def fly(self) -> bool:
pass
def quack(self):
pass
@implements(Flyable)
@implements(Quackable)
class MallardDuck(Duck):
def __init__(self, age):
super(MallardDuck, self).__init__(age)
def migrate(self, dir):
return True
def fly(self):
pass
The above would throw the following errors:
.. code-block:: python
NotImplementedError: 'MallardDuck' must implement method 'fly((self) -> bool)' defined in interface 'Quackable'
NotImplementedError: 'MallardDuck' must implement method 'quack((self))' defined in interface 'Quackable'
NotImplementedError: 'MallardDuck' must implement method 'migrate((direction))' defined in interface 'Flyable'
You can find a more detailed example in example.py and by looking at tests.py.
Justification
There are currently two idiomatic ways to rewrite the above example.
The first way is to write base classes with mixins raising NotImplementedError in each method.
.. code-block:: python
class Duck:
def __init__(self, age):
self.age = age
class Flyable:
@staticmethod
def migrate(direction):
raise NotImplementedError("Flyable is an abstract class")
def fly(self) -> str:
raise NotImplementedError("Flyable is an abstract class")
class Quackable:
def fly(self) -> bool:
raise NotImplementedError("Quackable is an abstract class")
def quack(self):
raise NotImplementedError("Quackable is an abstract class")
class MallardDuck(Duck, Quackable, Flyable):
def __init__(self, age):
super(MallardDuck, self).__init__(age)
def migrate(self, dir):
return True
def fly(self):
pass
But there are a couple drawbacks implementing it this way:
-
We would only get a
NotImplementedErrorwhen callingquackwhich can happen much later during runtime. Also, raisingNotImplementedErroreverywhere looks clunky. -
It's unclear without checking each parent class where super is being called.
-
Similarly the return types of
flyinFlyableandQuackableare different. Someone unfamiliar with Python would have to read up onMethod Resolution Order <https://www.python.org/download/releases/2.3/mro/>_. -
The writer of
MallardDuckmade methodmigratean instance method and renamed the argument todirwhich is confusing. -
We really want to be differentiating between behavior and inheritance.
The advantage of using implements is it looks cleaner and you would get errors at import time instead of when the method is actually called.
Another way is to use abstract base classes from the built-in abc module:
.. code-block:: python
from abc import ABCMeta, abstractmethod, abstractstaticmethod
class Duck(metaclass=ABCMeta):
def __init__(self, age):
self.age = age
class Flyable(metaclass=ABCMeta):
@abstractstaticmethod
def migrate(direction):
pass
@abstractmethod
def fly(self) -> str:
pass
class Quackable(metaclass=ABCMeta):
@abstractmethod
def fly(self) -> bool:
pass
@abstractmethod
def quack(self):
pass
class MallardDuck(Duck, Quackable, Flyable):
def __init__(self, age):
super(MallardDuck, self).__init__(age)
def migrate(self, dir):
return True
def fly(self):
pass
Using abstract base classes has the advantage of throwing an error earlier on instantiation if a method is not implemented; also, there are static analysis tools that warn if two methods have different signatures. But it doesn't solve issues 2-4 and implements will throw an error even earlier in import. It also in my opinion doesn't look pythonic.
Credit
Implementation was inspired by a PR <https://github.com/pmatiello/python-interface/pull/1/files>_ of @elifiner.
Test
Running unit tests::
make test
Running linter::
make lint
Running tox::
make test-all
License
Apache License v2
Related Skills
node-connect
350.1kDiagnose OpenClaw node connection and pairing failures for Android, iOS, and macOS companion apps
claude-opus-4-5-migration
109.9kMigrate prompts and code from Claude Sonnet 4.0, Sonnet 4.5, or Opus 4.1 to Opus 4.5
frontend-design
109.9kCreate distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, or applications. Generates creative, polished code that avoids generic AI aesthetics.
model-usage
350.1kUse CodexBar CLI local cost usage to summarize per-model usage for Codex or Claude, including the current (most recent) model or a full model breakdown. Trigger when asked for model-level usage/cost data from codexbar, or when you need a scriptable per-model summary from codexbar cost JSON.
