I l@ve RuBoard Previous Section Next Section

5.21 Delegating Messages to Multiple Objects

Credit: Eduard Hiti

5.21.1 Problem

You need to multiplex messages (attribute requests) to several objects that share the same interface.

5.21.2 Solution

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"

5.21.3 Discussion

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

Recipe 5.8 and Recipe 5.9; documentation for the operator built-in module in the Library Reference.

    I l@ve RuBoard Previous Section Next Section