[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