One issue with generic functions and adaptation as currently being discussed
(and something Tim mentioned a while back), it that it is very focused on
dispatching based solely on obj.__class__.__mro__.
That's all well and good, but (to use an example of Tim's), suppose we have a
couple of frameworks we're using, where those frameworks have each defined
their own version of a "wibbly-wobbly" object:
frmwrk_A.IWibble
frmwrk_B.IWobble
The methods and signatures included in these protocols may be the same,
IWibble may be a subset of IWobble, or vice versa.
Both frameworks have defined their interfaces (input and output) in terms of
these protocols, rather than in terms of concrete types.
As Tim pointed out, pure type-based dispatch would require that every type be
registered with both protocols, even if the two protocols are identical. Doing
that manually would be a serious pain if the frameworks were non-trivial.
The first thought may be to allow adaptation between protocols - but the issue
with that is that at adaptation time, we only have the object to work with,
and won't know what protocols it claims to implement (this is where either
__conform__ or a global registry comes in with PEP 246).
Even if we did know, the cost of doing a search on all equivalent protocols on
each call would add up. We don't really want to be finding ways to make
functions calls even slower than usual ;)
There is, however, an alternative, which would be to include a mechanism for
telling a protocol about other equivalent protocols, and updating the
registration mechanism to distribute any registration updates to the
equivalent protocols.
For example (not tested code!):
def _update(self, updates, updated=None):
# Update ourselves with a change
# This update does NOT overwrite existing mappings
changes = {}
for signature, adapter in updates.iteritems():
if signature not in self.registry:
changes[signature] = adapter
self.registry.update(changes)
self._distribute_update(changes, updated)
def _distribute_update(self, updates, updated=None):
# We don't want to get this update back
if updated is None:
updated = set()
else:
if self in updated:
return
updated.add(self)
# Update any equivalent protocols
# that have not yet been updated
for protocol in self.subprotocols:
if protocol not in updated:
protocol._update(updates, updated)
def _register_callable(self, signature, callable)
# Explicit registration overrides existing mapping
self.registry[signature] = adapter
self._distribute_update({signature:callable})
def register_subprotocol(self, protocol):
# Specified protocol is a subset of this one
# so it can safely use this protocol's adapters
protocol._update(self.registry)
self.subprotocols.add(protocol)
def register(self, *key)
def helper(adapter):
self.register(adapter, *key)
return adapter
return helper
Using such a mechanism, the interface mismatch above could be addressed in one
of the following ways:
Suppose framework A specifies fewer methods than framework B, but those
methods match. Then you can write:
frmwrk_B.IWobble.register_subprotocol(frmwrk_A.IWibble)
# Now registration for framework B also registers you for the narrower
# interface in framework A
You can turn that around, if A is the one that is more prescriptive:
frmwrk_A.IWibble.register_subprotocol(frmwrk_B.IWobble)
# Now it is registration for framework A that registers you for the narrower
# interface in framework B
And finally, if the two interfaces are identical:
frmwrk_A.IWibble.register_subprotocol(frmwrk_B.IWobble)
frmwrk_B.IWobble.register_subprotocol(frmwrk_A.IWibble)
# Now registration for either framework registers you for both
Now, suppose, however, that mapping from A to B required a slight tweak to A's
interface - one of the method signatures didn't line up right (e.g. one of A's
methods has the wrong name). This can be handled with an explicit protocol
adapter, which would provide a modified update method like so:
class ProtocolAdapter(object):
def __init__(self, src, target):
self.target = target
src.register_subprotocol(self)
def _update(self, updates, updated=None):
if self.target in updated:
return
wrapped_updates = {}
for signature, adapter in updates.iteritems():
def wrapped_adapter(*args, **kwds):
return self.adapt(adapter(*args, **kwds))
wrapped_updates[signature] = wrapped_adapter
self.target._update(wrapped_updates, updated)
def __call__(self, adapter):
self.adapt = adapter
class AdaptWibbletoWobble(object):
def __init__(self, obj):
self.obj = obj
def __iter__(x, y, z):
return self.obj.method2(x, y, z)
ProtocolAdapter(frmwork_A.IWibble, frmwrk_B.IWobble)(AdaptWibbleToWobble)
The equivalent of the above for generic functions is the case where "generic
function A" does a very similar thing to "generic function B", and you want to
be able to do a wholesale delegation from A to B of everything that B handles
more specifically than A.
Aside from the fact that I think any such transitivity mechanism should be
observer based, and that the updates should happen at registration time rather
than lookup time, I'm not really wedded to any of the implementation details
above. But I figured it was something worth throwing out there :)
Cheers,
Nick.
--
Nick Coghlan | [EMAIL PROTECTED] | Brisbane, Australia
---------------------------------------------------------------
http://www.boredomandlaziness.org
_______________________________________________
Python-3000 mailing list
[email protected]
http://mail.python.org/mailman/listinfo/python-3000
Unsubscribe:
http://mail.python.org/mailman/options/python-3000/archive%40mail-archive.com