[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