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
[email protected]
http://mail.python.org/mailman/listinfo/python-3000
Unsubscribe:
http://mail.python.org/mailman/options/python-3000/archive%40mail-archive.com