Python has no builtin concept of interfaces. In general this is no big deal, but I have found myself in situations where a slightly more enforced contract between classes would have been useful. Python comes with a similar thing called abstract base classes, which are meant to serve the purpose of an interface; however they only care about methods being implemented, but not their signature. In one of my tools I wanted to expose a single API, while under the hood use two different GUI libraries (which ever is available). Being consistent with method signatures was important to avoid those implementations drifting too far away from each other. I only pursued it to some degree as it felt like trying to force Python into something it just isn't, but maybe it is useful for someone else out there. Here is my approach to a signature-aware interface base class:
import inspect
class AbstractInterface(type):
"""A metaclass to build interfaces.
Any derived class will be an interface that can be used as a meta
class for concrete classes. The interface's methods (properties
included) must be implemented with the same signature in the
concrete class, otherwise its definition will fail.
"""
def __new__(meta, clsname, bases, clsdict):
metaname = meta.__name__
metadict = meta.__dict__
def prop(thing):
return isinstance(thing, property)
def callable_or_prop(thing):
return callable(thing) or prop(thing)
must_implement = [k for k, v in metadict.items()
if callable_or_prop(v)]
for methodname in must_implement:
methodident = "{clsname}.{methodname}()".format(**locals())
try:
clsmethod = clsdict[methodname]
except KeyError:
raise NotImplementedError(
"{} is missing.".format(methodident))
metamethod = metadict[methodname]
if prop(metamethod):
if not prop(clsmethod):
raise NotImplementedError(
"{} must be a property.".format(methodident))
continue # Properties have no ArgSpec.
metaspec = inspect.getargspec(metamethod)
clsspec = inspect.getargspec(clsmethod)
if not metaspec == clsspec:
msg = ("Signatures do not match:\n"
"{metaname}.{methodname}()\t{metaspec}\n"
"{clsname}.{methodname}()\t{clsspec}"
"".format(**locals()))
raise NotImplementedError(msg)
return super(AbstractInterface, meta).__new__(meta, clsname,
bases, clsdict)
Now to define an interface we could do:
class ICar(AbstractInterface):
def honk(self, volume=10, length=2):
pass
def accelerate(self, stepsize):
pass
@property
def speed(self):
pass
Applying the interface as a metaclass will enforce its methods/properties to be implemented:
class SportsCar(object):
__metaclass__ = ICar
@property
def speed(self):
pass
def accelerate(self, stepsize):
pass
def honk(self, volume=10, length=2):
pass
A missing method or differing method (in regards to variable name, default value, being a property) will raise a
NotImplementedError
during class definition. Cheers!