Python Cookbook 2Nd Edition Jun 1002005 [Electronic resources]

David Ascher, Alex Martelli, Anna Ravenscroft

نسخه متنی -صفحه : 394/ 385
نمايش فراداده

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):
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

We follow the natural convention that any class can 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'
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)

Any class that uses MetaInterfaceChecker as its 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):
''' 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( )

Any code dealing with an instance of such a class can choose to check whether it can rely on certain interfaces:

def use(sk):
if IMinimalMapping in sk._ _implements_ _:
...code using 'sk[...]' and/or 'x in sk'...

You can, if you want, provide much fancier and more thorough checks, 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.