The design problem described in the last article is clearly a problem
of interfaces. We have an object and we want to interact with it
through multiple interfaces (GUI, HTTP, FTP, etc.). The mixin
solution just adds a bunch of methods for each interface, with the
result of creating a monster object with hundreds of methods.
This design is a maintenance nightmare. The human brain can manage a
limited amount of information. An object with ten methods can be
kept in mind easily enough, but an object with a hundred methods is
outside the reach of the average programmer.
The solution is to split the hundred methods into ten
categories with ten methods each: at this point you can keep the ten
categories in your mind.
This solution scales well: if I
need a thousand methods, I can just define ten macro-categories,
each macro-category including ten micro-categories, and I can keep
in mind the macro-categories.
Hierarchical categories are the natural
way to memorize information for the human mind, at least from
Aristotle's times: this is the right solution, not to have
a hundred methods at the same level in the same namespace.
We need therefore a mixin-like solution which keeps the methods
in separate namespaces explicitly (usual mixins keeps the methods
in separate namespaces but implicitly, without visibility to the
use of the class).
Technically this idea can be implemented by defining an
interface wrapper object
which is also an attribute descriptor (if you are not
familiar with the concept of descriptors in Python, you must read
the beautiful essay by R. Hettinger on the subject):
class BaseWrapper(object):
"Base class for interface wrappers built from mixin classes"
def __init__(self, obj):
self.__obj = obj
def __get__(self, obj, objcls=None):
if obj is None:
return self
return self.__class__(obj)
def __getattr__(self, name):
obj = self.__obj
if obj is None:
raise TypeError('Unbound wrapper %r' % self)
return getattr(obj, name)
def __repr__(self):
names = ', '.join(n for n in dir(self) if not n.startswith('_'))
msg = 'bound to %r' % self.__obj if self.__obj is not None else ''
return '<%s {%s} %s>' % (self.__class__.__name__, names, msg)
def iwrapper(mixin):
"Convert a mixin class into an interface wrapper object"
# could be implemented with single-inheritance only, but why bother?
cls = type(mixin.__name__ + 'Wrapper', (BaseWrapper, mixin), {})
return cls(None)
Interface wrapper objects are instances of the mixin (interface) class
but also proxies: they are intended to wrap the inner
object, by dispatching first on the mixin methods and then to the inner
methods. In practice, if you want to add an interface to an instance c
of a class C, and the methods of the interface are stored into a mixin
class M, you can just add an interface wrapper to C:
class C(object):
m = iwrapper(M)
Now c.m is an instance of M which can also be used as a fake
C object thanks to the __getattr__ trick. For simplicity I
assuming that there are no name clashes between the names of the
interface methods and the names of the inner methods.
Here is how you would use interface wrappers in the PictureContainer
example:
class PictureContainer(DictMixin, object):
# interface wrappers are instances of the corresponding mixin class;
# moreover they dispatch on PictureContainer objects
gui = iwrapper(GUI)
http = iwrapper(HTTP)
webdav = iwrapper(WEBDAV)
ftp = iwrapper(FTP)
auth = iwrapper(AUTH)
@property
def log(self):
return logging.getLogger(self.__class__.__name__)
def __init__(self, id, pictures_or_containers):
self.id = id
self.data = {}
for poc in pictures_or_containers:
# both pictures and containers must have an .id
self.data[poc.id] = poc
def __getitem__(self, id):
return self.data[id]
def __setitem__(self, id, value):
self.log.info('Adding or replacing %s into %s', id, self.id)
self.data[id] = value
def __delitem__(self, id):
self.log.warn('Deleting %s', id)
del self.data[id]
def keys(self):
return self.data.keys()
Notice that I have refactored PictureContainer a bit. I have
defined the property log explicitly (before it was imported,
from utility import log). Since it takes only three lines, it
makes sense to write them out and to avoid forcing the reader to look
in another module. There is always a compromise between code reuse and
spaghetti code. When in doubt, I always remind myself that
readability counts.
As you see, I have removed all mixin classes except DictMixin.
After all, I have decided that a PictureContainer is a dictionary,
but it is not really also an object of type GUI, HTTP, WEBDAV, FTP, AUTH.
Logically those are different interfaces or wrappers over the basic object.
There is still multiple inheritance from object, because
DictMixin is an old-style class (for backward compatibility reasons)
whereas interface wrappers, being attribute descriptors,
are intended to be used with new-style
classes. Inheriting from object makes PictureContainer a new style
class. This is one of the rare cases where multiple inheritance is
convenient, but this use case has already disappeared in Python 3.0,
where all classes are new-style.
Let me check if this solution to our design problem is consistent
with the Zen di Python.
First of all, the implementation of the interface wrapper concept is simple
- 20 lines of code - and this is already a step in the right direction
(if the implementation is hard to
explain, it's a bad idea).
All the methods of the mixin are localized in the mixin namespace
and they do not pollute the namespace of the original class, and
that's good (namespaces are one honking great idea -- let's do more of
those!).
Moreover, we are following the explicit is better than implicit principle.
For instance, to access the POST method of the HTTP mixin we need
to write self.http.POST, which is good, since the readers of our code
will not need to guess the origin of the method
(in the face of ambiguity, refuse the temptation to guess).
The solution is also usable (practicality beats purity): you can instantiate
a PictureContainer object and perform some experiment from the Python
prompt:
>>> pc = PictureContainer('root', [])
Autocompletion works pretty well:
>>> pc.ftp. # press TAB
pc.ftp.RECV
pc.ftp.SEND
...
the help function does not provide excessive information
>>> help(pc)
Help on PictureContainer in module mixins2 object:
class PictureContainer(UserDict.DictMixin, __builtin__.object)
| Method resolution order:
| PictureContainer
| UserDict.DictMixin
| __builtin__.object
|
| Methods defined here:
| ...
|
| auth = <AUTHWrapper {is_admin ...} >
| ftp = <FTPWrapper {RECV, SEND ...} >
| gui = <GUIWrapper {draw_button, draw_buttons ...} >
| http = <HTTPWrapper {GET, POST ...} >
| webdav = <WEBDAVWrapper {GET, POST, LOCK, UNLOCK ...} >
...
and it is possible to introspect just the features you are interested
in, without having everything mixed in (sparse is better than dense):
>>> print dir(pc.http)
['GET, 'POST', ...]
Is the solution presented here the only solution or even the best
solution to the problem of adding multiple interfaces to an object?
Certainly not. I have written it down in half an hour an I am not
even using it, since (fortunately) I am not a framework writer.
The solution is intended as a suggestion
for people which are refactoring a framework based
on mixins and which have their methods organized in mixin
classes. Then, the iwrapper function is able to convert
such pre-existing classes into objects which can be used as
class attributes, replacing multiple inheritance with composition.
If you do not already have the mixin classes, you may be better
off with a different solution. Moreover, if you are using Python 2.6
or later, it is natural to tackle this problem in terms of
Abstract Base Classes (ABC), which I have completely ignored.
The solution I have presented lists all the interfaces supported
by an object directly (statically); however you could use a different
design based on adapters, where the object is dynamically adapted
with the correct interface before being passed to its consumer object.
A solution based on adapters is fine if the list of supported
interfaces is not know a priori.
My point here was to show here that Python (at least from Python 2.2)
makes it easy to implement solutions based on composition rather than on
inheritance.
Actually, I would say that the general trend of modern Python
frameworks is to favor component programming over
inheritance. You should take in account this fact. Instead of my home
made solution you may want to try out an enterprise-ready solution,
like the component framework of Zope 3 (I personally prefer home made
solutions to over-engineered frameworks, but YMMV).
Nowadays I tend to consider multiple inheritance and mixins more of
a hack than a legitimate design technique: they may be useful when you
need to integrate with pre-existing code with a minimal offert, or as
a debugging tool, when you want to instrument a third party hierarchy,
but you if are designing an application from scratch you are often
better off if you do not rely on mixins. Actually I usually
recommend to use as little as possible even single inheritance.