There's a way to accomplish something similar using issubclass() or isinstance() rather than implements(). This works by implementing a new BaseInterface class you can use to derive proper interfaces (that is, types that define a list of methods that need to be implemented, but can't provide a default implementation or be instantiated).
Implementing BaseInterface
First we need a new metaclass for interface-like types. This will provide the desired behaviour of issubclass() and isinstance() and prevent the Interfaces from being instantiated by mistake. At the bottom of the snippet there's an empty BaseInterface class that custom interfaces can be derived from.
class InterfaceMeta(type):
"""A metaclass for defining interfaces.
An interface defines a set of public methods and attributes needed
to interact with an object in a particular way, but is never
instantiated directly.
"""
def __new__(meta, name, bases, attrs):
"""Prevent interfaces from overriding __new__, __init__, or __call__."""
banned_methods = ('__new__', '__init__', '__call__')
if any([banned in attrs for banned in banned_methods]):
# Someone has tried to make it possible to instantiate an Interface
raise AttributeError(
'It is illegal to instantiate an Interface. '
'Therefore, it is illegal to implement any of the '
'following methods: {}'.format(', '.join(banned_methods))
)
else:
return super().__new__(meta, name, bases, attrs)
def __call__(cls, *args, **kwargs):
"""Prevent interfaces from being instantiated."""
raise NotImplementedError("Interface cannot be instantiated.")
def __instancecheck__(cls, instance):
"""Control the behaviour of `isinstance`.
An object is considered to be an instance of `cls` if it has
all of `cls`'s public attributes and methods.
"""
for cls_attr in dir(cls):
if cls_attr.startswith('_'):
# Private and protected attributes are not considered
# to be part of the interface.
continue
if not hasattr(instance, cls_attr):
# If `instance` is missing a public attribute of `cls`,
# it is not an instance of `cls`.
return False
elif (
callable(getattr(cls, cls_attr))
and not callable(getattr(instance, cls_attr))
):
# If `instance` is missing a public method of `cls`,
# it is not an instance of `cls`.
return False
return True
class BaseInterface(metaclass=InterfaceMeta):
"""Base class for all interfaces."""
pass
Example usage
Now we can define an custom interface by inheriting from BaseInterface.
class SomeInterface(BaseInterface):
"""A class defining an interface.
Other objects that have the same public methods and attributes
as `SomeInterface` are considered to be instances of `SomeInterface`.
"""
def name(self):
pass
def age(self):
pass
class Foo:
def name(self):
# Some implementation...
pass
def age(self):
# Some implementation...
pass
Since Foo implements all the public methods of SomeInterface, we get the following behaviour.
In [2]: issubclass(Foo(), SomeInterface)
Out[2]: True
In [3]: isinstance(Foo(), SomeInterface)
Out[3]: True
More details here.