[Python-3000] Adaptation vs. Generic Functions (original) (raw)

Tim Hochberg tim.hochberg at ieee.org
Wed Apr 5 15:55:26 CEST 2006


Nick Coghlan wrote:

Tim Hochberg wrote:

So after all of that, I think my conclusion is that I wouldn't refactor this at all, at least not yet. I'd add support for multiple registration and possibly spell adapt as call, otherwise I'd leave it alone. My opinion may change after I try ripping out keysof and see how it looks. I was curious to see how the adaptation version actually looked with your and Guido's versions mixed. While writing it, I also noticed two interesting cases worth simplifying: 1. the "no adapter needed case" (for registering that a type implements a protocol directly) 2. the "missing adapter case" (for providing a default adaptation, as in the generic function case) Here's what the whole thing ended up looking like: def nulladapter(*args): """Adapter used when adaptation isn't actually needed""" if len(args) > 1: return args else: return args[0] class Protocol(object): """Declare a named protocol""" def init(self, name): self.registry = {} self.name = name def register(self, adapter, *keys): """Register an adapter from given registry keys to the protocol""" if adapter is None: adapter = nulladapter for key in keys: self.registry[key] = adapter def registerfor(self, *keys): """Function decorator to register as an adapter for given keys""" def helper(adapter): self.register(adapter, *keys) return adapter return helper def candidatekeys(self, callargs): """Find candidate registry keys for given call arguments""" # Default behaviour dispatches on the type of the first argument return type(callargs[0]).mro def defaultadapter(self, *args): """Call result when no adapter was found""" raise TypeError("Can't adapt %s to %s" % (args[0].class.name, self.name)) def call(self, *args): """Adapt supplied arguments to this protocol""" for key in self.candidatekeys(args): try: adapter = self.registry[key] except KeyError: pass else: return adapter(*args) return self.defaultadapter(*args)

I like this version. The naming seems an improvement and the default_adapter seems like a worthy addition. I do keep wondering if there's some reason for a user of Protocol to call candidate_keys directly or it's only an implementation detail. If the there is such a reason, and we could figure it out, it would probably immediately obvious what to call it. If there isn't, perhaps it should be prefixed with '_' to indicate that it's not part of the public interface.

# The adapting iteration example class AdaptingIterProtocol(Protocol): def init(self): Protocol.init(self, "AdaptingIter")

def defaultadapter(self, obj): if hasattr(obj, "iter"): return obj.iter() raise TypeError("Can't iterate over a %s object" % obj.class.name) AdaptingIter = AdaptingIterProtocol() AdaptingIter.register(SequenceIter, list, str, unicode) @AdaptingIter.registerfor(dict) def AdaptingDictIter(obj): return SequenceIter(obj.keys())

# Building a generic function on top of that Protocol class GenericFunction(Protocol): def init(self, default): Protocol.init(self, default.name) self.doc = default.doc self.defaultadapter = default def candidatekeys(self, callargs): """Find candidate registry keys for given call arguments""" argtypes = tuple(type(x) for x in callargs) if len(callargs) == 1: yield argtypes[0] # Allow bare type for single args yield argtypes # Always try full argument tuple # The generic iteration example @GenericFunction def GenericIter(obj): """This is the docstring for the generic function.""" # The body is the default implementation if hasattr(obj, "iter"): return obj.iter() raise TypeError("Can't iterate over %s object" % obj.class.name) @GenericIter.register(list) def GenericSequenceIter(obj): return SequenceIter(obj) GenericIter.register(str)(GenericSequenceIter) GenericIter.register(unicode)(GenericSequenceIter) @GenericIter.register(dict) def GenericDictIter(obj): return SequenceIter(obj.keys())

These should all be "GenericIter.register_for", right?

Regards,

-tim



More information about the Python-3000 mailing list