Recipe 20.9. Checking Whether Interfaces Are Implemented
Credit: Raymond Hettinger
Problem
You want to ensure that the classes you define implement the
interfaces that they claim to implement.
Solution
Python does not have a formal concept of
"interface", but we can easily
represent interfaces by means of
"skeleton" classes such as:
class IMinimalMapping(object):We follow the natural convention that any class can
def _ _getitem_ _(self, key): pass
def _ _setitem_ _(self, key, value): pass
def _ _delitem_ _(self, key): pass
def _ _contains_ _(self, key): pass
import UserDict
class IFullMapping(IMinimalMapping, UserDict.DictMixin):
def keys(self): pass
class IMinimalSequence(object):
def _ _len_ _(self): pass
def _ _getitem_ _(self, index): pass
class ICallable(object):
def _ _call_ _(self, *args): pass
represent an interface: the interface is the
set of methods and other attributes of the class. We can say that a
class C implements
an interface i if
C has all the methods and other attributes
of i (and, possibly, additional ones).We can now define a simple custom metaclass that checks whether
classes implement all the interfaces they claim to implement:
# ensure we use the best available 'set' type with name 'set'Any class that uses MetaInterfaceChecker as its
try:
set
except NameError:
from sets import Set as set
# a custom exception class that we raise to signal violations
class InterfaceOmission(TypeError):
pass
class MetaInterfaceChecker(type):
''' the interface-checking custom metaclass '''
def _ _init_ _(cls, classname, bases, classdict):
super(MetaInterfaceChecker, cls)._ _init_ _(classname, bases, classdict)
cls_defines = set(dir(cls))
for interface in cls._ _implements_ _:
itf_requires = set(dir(interface))
if not itf_requires.issubset(cls_defines):
raise InterfaceOmission, list(itf_requires - cls_defines)
metaclass must expose a class attribute _ _implements_
_, an iterable whose items are the interfaces the class
claims to implement. The metaclass checks the claim, raising an
InterfaceOmission exception if the claim is false.
Discussion
Here's an example class using the
MetaInterfaceChecker custom metaclass:
class Skidoo(object):Any code dealing with an instance of such a class can choose to check
''' a mapping which claims to contain all keys, each with a value
of 23; item setting and deletion are no-ops; you can also call
an instance with arbitrary positional args, result is 23. '''
_ _metaclass_ _ = MetaInterfaceChecker
_ _implements_ _ = IMinimalMapping, ICallable
def _ _getitem_ _(self, key): return 23
def _ _setitem_ _(self, key, value): pass
def _ _delitem_ _(self, key): pass
def _ _contains_ _(self, key): return True
def _ _call_ _(self, *args): return 23
sk = Skidoo( )
whether it can rely on certain interfaces:
def use(sk):You can, if you want, provide much fancier and more thorough checks,
if IMinimalMapping in sk._ _implements_ _:
...code using 'sk[...]' and/or 'x in sk'...
for example by using functions from standard library module
inspect to check that the attributes being exposed
and required are methods with compatible signatures. However, this
simple recipe does show how to automate the simplest kind of checks
for interface compliance.
See Also
Library Reference and Python in a
Nutshell docs about module sets, (in
Python 2.4 only) the set built-in, custom
metaclasses, the inspect module.