[Python-3000] Adaptation [was:Re: Iterators for dict keys, values, and items == annoying :)]
Nick Coghlan
ncoghlan at gmail.com
Sun Apr 2 06:29:27 CEST 2006
Greg Ewing wrote:
> Alex Martelli wrote:
>
>> The existence of dicts is
>> a laughably feeble excuse to avoid having adaptation in Python's
>> standard library,
>
> The point is whether it buys you anything important
> over using a dict. If you use it in such a wide-open
> way that it's just a mapping from anything to anything,
> I don't see that it does.
>
> Another thing that makes me way of adaptation is that
> it relies on a global registry. I'm leery about global
> registries in general.
>
> One problem is that, because they're global, you only
> get one of them per program. I actually think the very
> existince of copy_reg is wrongheaded, because it assumes
> that in any given program there will be one correct way
> to copy any given type of object. On the extremely
> rare occasions when I want to deep-copy something, I
> have very specific ideas on how deeply I want to copy
> it, and that could vary from one situation to another.
> I wouldn't trust anything found in a global registry
> to do the right thing.
>
> Another problem is that, because they're global,
> any part of the program can put stuff in them that
> gets used by any other part, without its explicit
> knowledge. This can make it hard to tell what any
> given piece of code is going to do without searching
> the whole program.
This is exactly what bothers me about the concept. Adaptation strikes me as
very easily becoming an attractive nuisance, analogous to implicit type
conversions in C++ and VB6.
This is where I've always come unstuck in thinking about adaptation - actually
using C++ and VB6 has persuaded me that implicit type conversions are
generally evil, and there doesn't seem to be anything in adaptation that makes
it the exception.
OTOH, there may be a hidden assumption among the fans of adaptation that
adaptation to a mutable interface should never add state to, nor copy the
state of, an adapted object. Any mutation made via an adaptor would be
reflected as a mutation of the original object. Adaptation to immutable
interfaces would always be fine, naturally. If that's an unwritten rule of
adaptation, then:
1. It addresses the main evil of implicit type conversion (hidden state)
2. It needs to become a *written* rule, so that anyone writing a stateful
adapter can be duly admonished by their peers
I seem to recall PJE making a point along those lines during the last big PEP
263 discussion. . .
The other thing is that it makes more sense to me for there to be a
per-protocol type->adapter registry, rather than a global registry with tuples
of source type/target protocol pairs.
Secondly, given that each framework is likely to be defining the protocols
that it consumes, I don't see the problem with each one defining its *own*
adaptation registry, rather than having one mega-registry that adapts
everything to everything.
In its own registry, a library/framework would preregister:
1. Its own types that can be adapted to the defined protocols
2. Types from any libraries/frameworks it uses that can be adapted
A user of the framework would then:
1. Register any external types that can be adapted
2. Use the framework's registry when consuming an interface defined there
Concerns about naming conflicts go away, because that is resolved by having
each framework store its protocols in different registries (i.e. the Python
module namespace is used to disambiguate protocol names). Whether protocols
are identified via string names, interface classes, or what have you is then
also a per-framework decision. Decisions about default behaviour,
transitivity, and so on and so forth are also up to the framework.
Then the role of an adaptation module in the standard library would be to
provide a standard API for per-framework registries, without also providing a
mega-registry for adapting everything to everything.
For example:
# ---- adapt.py ----
# sans error handling. . .
class AdapterRegistry(object):
def __init__(self, protocol):
self.adapters = {}
self.protocol = protocol
def register_adapter(self, source_type, adapter):
self.adapters[source_type] = adapter
def find_adapter(self, source_type):
return self.adapters[source_type]
def adapt(self, obj):
return self.adapters[type(obj)](obj)
class ProtocolRegistry(object):
def __init__(self, *protocols):
self._protocols = {}
for protocol in protocols:
self._protocols[protocol] = AdapterRegistry(protocol)
def register_adapter(self, source_type, target_protocol, adapter):
self._protocols[target_protocol].register_adapter(source_type, adapter)
def find_adapter(self, source_type, target_protocol):
return self._protocols[target_protocol].find_adapter(source_type)
def adapt(self, obj):
return self._protocols[target_protocol].adapt(obj)
# Used like:
copy_registry = ProtocolRegistry("copy", "deepcopy")
copy_adapters = copy_registry["copy"]
copy_adapters.register_adapter(int, functools.ident)
copy_adapters.register_adapter(float, functools.ident)
etc. . .
deepcopy_adapters = copy_registry["deepcopy"]
copy_adapters.register_adapter(int, functools.ident)
copy_adapters.register_adapter(float, functools.ident)
etc. . .
Cheers,
Nick.
--
Nick Coghlan | ncoghlan at gmail.com | Brisbane, Australia
---------------------------------------------------------------
http://www.boredomandlaziness.org
More information about the Python-3000
mailing list