|I l@ve RuBoard|
5.21 Delegating Messages to Multiple Objects
Credit: Eduard Hiti
As usual, this task is best wrapped in a class:
import operator # faster in Python 2.2, but we also handle any release from 2.0 and later try: dict except: from UserDict import UserDict as dict class Multiplex(dict): """ Multiplex messages to registered objects """ def _ _init_ _(self, objs=): dict._ _init_ _(self) for alias, obj in objs: self[alias] = obj def _ _call_ _(self, *args, **kwargs): """ Call registered objects and return results through another Multiplex. """ return self._ _class_ _( [ (alias, obj(*args, **kwargs)) for alias, obj in self.items( ) ] ) def _ _nonzero_ _(self): """ A Multiplex is true if all registered objects are true. """ return reduce(operator.and_, self.values( ), 1) def _ _getattr_ _(self, name): """ Wrap requested attributes for further processing. """ try: return dict._ _getattr_ _(self, name) except: # Return another Multiplex of the requested attributes return self._ _class_ _( [ (alias, getattr(obj, name) ) for alias, obj in self.items( ) ] )
As usual, this module is also invokable as a script, and, when run that way, supplies a self-test (or, here, a demo/example):
if _ _name_ _ == "_ _main_ _": import StringIO file1 = StringIO.StringIO( ) file2 = StringIO.StringIO( ) delegate = Multiplex( ) delegate[id(file1)] = file1 delegate[id(file2)] = file2 assert not delegate.closed delegate.write("Testing") assert file1.getvalue() == file2.getvalue( ) == "Testing" delegate.close( ) assert delegate.closed print "Test complete"
A Multiplex object exposes the same interface as the multiplexed registered object targets. Multiplexing doesn't work for the dictionary interface, since that is used by the Multiplex class itself. We take care to ensure that all attributes of a dictionary object are indeed accessed in the way one deals with dictionaries. Note that this interferes with delegating such attribute names as 'items', 'keys', 'values', and 'get'. If this is a problem for your application, you can avoid inheriting Multiplex from dict, have Multiplex use a dict by containment instead, and give it another interface. However, whatever names you do decide to put on the public interface will still not be subject to multiplexed delegation.
Attributes of individual registered objects can be accessed by the alias used to register them for multiplexed delegation:
delegate["test"] = aClass( ) print delegate.aClassAttribute["test"]
Message chains are also possible:
print delegate.aClassAttribute.aMethod( )
This calls aMethod on aClassAttribute from all multiplex targets.
Behind the scenes, as a result of how Multiplex._ _getattr_ _ is coded, delegate.aClassAttribute returns another Multiplex object, as does the .aMethod (which collects bound methods into the other anonymous Multiplex). Finally, the special method Multiplex._ _call_ _ enters the scene, and Multiplex delegates the call operation to each of the bound methods, collecting their results into yet another Multiplex.
The design choice for Multiplex._ _nonzero_ _ is, of course, quite debatable. As coded in the recipe, it makes a Multiplex true if all the registered objects are true, including when there are no registered objects at all, which may be a bit counterintuitive. Depending on your application, you might therefore want to code this quite differently. Be sure to look at Recipe 5.9 for a different approach to a similar problem.
5.21.4 See Also
|I l@ve RuBoard|