[Python-3000] Adaption & generic functions [was Generic functions]

Tim Hochberg tim.hochberg at cox.net
Tue Apr 4 20:17:03 CEST 2006


Considering generic function in combination adaption led to one more 
small change in the Protocol implementation that I'm playing with, and 
all of the sudden I'm left with something that I could actually use. 
What I realized was that you could factor out the part that potentially 
has a lot of variation and suddenly you have a relatively simple 
framework that's extensible in all sorts of ways. By default, everything 
is the same as in the last iteration, but now it's much easier to change 
the behaviour by subtyping.

Let's return to the case that first bugged me about T->P dispatch. I 
have some tagged values and I want to assemble two of them  to form a 
complex number.

First we need a tagged value type. In reality I'd have this already

 >>> class TV(object):
...     def __init__(self, value, tag):
...         self.value = value
...         self.tag = tag
       
Then we need to subclass Protocol. I change the behaviour of keysof so 
that now things are looked up in the registry strictly based on their 
tags. Note that this can take an arbitrary number of arguments. In the 
default implementation (see below) only a single argument is allowed, 
which gives you the basic adapter(obj) -> newobj behaviour.

 >>> class TVProtocol(Protocol):
...     def keysof(self, *args):
...         try:
...             yield tuple(x.tag for x in args)
...         except AttributeError:
...             pass
 >>> ascomplex = TVProtocol('complex_from_tagged')

Then I define some converters:

 >>> import cmath
 >>> @ascomplex.when(('real', 'imag'))
... def complex_from_real_imag(real, imag):
...     return real.value + 1j*imag.value
 >>> @ascomplex.when(('mag', 'angle'))
... def complex_from_mag_angle(mag, angle):
...     return mag.value * cmath.exp(1j * cmath.pi / 180 * angle.value)
 >>> @ascomplex.when(('db', 'angle'))
... def complex_from_db_angle(db, angle):
...     return 10**(db.value/20.0) * cmath.exp(1j * cmath.pi / 180 * 
angle.value)

Here's some values that I can assume came from elsewhere:

 >>> tv_re, tv_im = TV(1, 'real'), TV(2, 'imag')
 >>> tv_db, tv_ang, tv_mag = TV(0, 'db'), TV(90, 'angle'), TV(2, 'mag')

And here's how I'd use it:

 >>> ascomplex(tv_re, tv_im)
(1+2j)
 >>> ascomplex(tv_db, tv_ang)
(6.1230317691118863e-017+1j)
 >>> ascomplex(tv_mag, tv_ang)
(1.2246063538223773e-016+2j)
 >>> ascomplex(tv_db, tv_mag)
Traceback (most recent call last):
 ...
ValueError: adapter not found


All of the sudden this is looking like something I could probably use. I 
also tried a simple generic function implementation on top of this (no 
inheritance, keysof just returned a tuple of types). That was also easy. 
Could full blown generic dispatch be added just by subclassing and 
adding the correct, and obviously much more complex, version of keysof? 
It seems likely, but I'm not certain. If so, this is starting to look 
like a very promising approach.

The updated Protocol implementation is below.

Regards,

-tim


class Protocol(object):
    all_protocols = set()
    def __init__(self, name, doc=''):
        self.name = name
        self.registry = {}
        self.__doc__ = doc
        self.all_protocols.add(self)
    def __repr__(self):
        return "<protocol %r>" % self.name
    __str__ = __repr__
    def __call__(self, *args):
        for key in self.keysof(*args):
            adapter = self.registry.get(key, None)
            if adapter is not None:
                return adapter(*args)
        raise ValueError('adapter not found')
    def keysof(self, *args):
        if len(args) != 1:
            raise TypeError("%s expects 1-argument, got %s" (self, 
len(args)))
        obj = args[0]
        mro = type(obj).__mro__
        for cls in mro:
            yield cls
    def register(self, adapter, *types):
        if not callable(adapter):
            raise TypeError("adapters must be callable")
        for t in types:
            self.registry[t] = adapter
    def when(self, *types):
        def decorator(adapter):
            self.register(adapter, *types)
            return adapter
        return decorator





More information about the Python-3000 mailing list