Recipe 20.9. Checking Whether Interfaces Are ImplementedCredit: Raymond Hettinger ProblemYou want to ensure that the classes you define implement the interfaces that they claim to implement. SolutionPython 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. DiscussionHere'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 AlsoLibrary Reference and Python in a Nutshell docs about module sets, (in Python 2.4 only) the set built-in, custom metaclasses, the inspect module. |