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 null_adapter(*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 = null_adapter > for key in keys: > self.registry[key] = adapter > > def register_for(self, *keys): > """Function decorator to register as an adapter for given keys""" > def helper(adapter): > self.register(adapter, *keys) > return adapter > return helper > > def candidate_keys(self, call_args): > """Find candidate registry keys for given call arguments""" > # Default behaviour dispatches on the type of the first argument > return type(call_args[0]).__mro__ > > def default_adapter(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.candidate_keys(args): > try: > adapter = self.registry[key] > except KeyError: > pass > else: > return adapter(*args) > return self.default_adapter(*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 default_adapter(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.register_for(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.default_adapter = default > > def candidate_keys(self, call_args): > """Find candidate registry keys for given call arguments""" > arg_types = tuple(type(x) for x in call_args) > if len(call_args) == 1: > yield arg_types[0] # Allow bare type for single args > yield arg_types # 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 _______________________________________________ Python-3000 mailing list Python-3000@python.org http://mail.python.org/mailman/listinfo/python-3000 Unsubscribe: http://mail.python.org/mailman/options/python-3000/archive%40mail-archive.com