[Python-Dev] Concrete proposals for PEP 246

Phillip J. Eby pje at telecommunity.com
Tue Jan 11 21:53:08 CET 2005


To save everyone from having to wade through the lengthy discussions 
between Alex and I, and to avoid putting all the summarization burden on 
Alex, I thought I would start a new thread listing my concrete proposals 
for PEP 246 changes, and summarizing my understanding of the current 
agreements and disagreements we have.  (Alex, please correct me if I have 
misrepresented your position in any respects.)


First, I propose to allow explicitly-declared Liskov violations, but using 
a different mechanism than the one Alex has proposed.  Specifically, I wish 
to remove all type or isinstance checking from the 'adapt()' 
function.  But, the 'object' type should have a default __conform__ that is 
equivalent to:

     class object:
         def __conform__(self,protocol):
             if isinstance(protocol,ClassTypes) and isinstance(self,protocol):
                 return self
             return None

and is inherited by all object types, including types defined by extension 
modules, unless overridden.

This approach provides solid backward compatibility with the previous 
version of PEP 246, as long as any user-implemented __conform__ methods 
call their superclass __conform__.  But, it also allows Liskov-violating 
classes to simply return 'None' to indicate their refusal to conform to a 
base class interface, instead of having to raise a special exception.  I 
think that, all else being equal, this is a simpler implementation approach 
to providing the feature, which Alex and others have convinced me is a 
valid (if personally somewhat distasteful) use case.


Second: Alex has agreed to drop the "cast" terminology, since its meanings 
in various languages are too diverse to be illuminating.  We have instead 
begun creeping towards agreement on some concepts such as "lossy" or 
"noisy" conversions.  I think we can also drop my original "lossy" term, 
because "noisy" can also imply information loss and better explains the 
real issue anyway.

A "noisy" conversion, then, is one where the conversion "makes up" 
information that was not implicitly present in the original, or drops 
information that *alters the semantics of the information that is 
retained*.  (This phrasing gets around Alex's LotsOfInfo example/objection, 
while still covering loss of numeric precision; mere narrowing and renaming 
of attributes/methods does not alter the semantics of the retained 
information.)

Adaptation is not recommended as a mechanism for noisy conversions, because 
implicit changes to semantics are a bad idea.  Note that this is actually 
independent of any transitivity issues -- implicit noisy conversion is just 
a bad idea to start with, which is why 'someList[1.2]' raises a TypeError 
rather than implicitly converting 1.2 to an integer!  If you *mean* to drop 
the .2, you should say so, by explicitly converting to an integer.

(However, it *might* be acceptable to implicitly convert 1.0 to an integer; 
I don't currently have a strong opinion either way on that issue, other 
than to note that the conversion is not "noisy" in that case.)

Anyway, I think that the current level of consensus between Alex and myself 
on the above is now such that his comparison to casting could now be 
replaced by some discussion of noisy vs. non-noisy (faithful? 
high-fidelity?) conversion, and the fact that adaptation is suitable only 
for the latter, supplemented by some examples of noisy conversion use cases 
and how to transform them into non-noisy constructs.  The string->file vs. 
string->file_factory example is a particularly good one, I think, because 
it shows how to address a common, practical issue.


Third: (Proposed) The PEP should explicitly support classic classes, or 
else there is no way to adapt exception instances.  (Yes, I have actually 
done this; peak.web adapts exception instances to obtain appropriate 
handlers, for example.)


Fourth: The principal issue from the original discussion that remains open 
at this time is determining specific policies or recommendations for 
addressing various forms of transitivity, which we have delved into a 
little bit.  (By  "open" I jut mean that there is no proposal for this 
issue currently on the table, not to imply that my proposals are not also 
"open" in the sense of awaiting consensus.)

Anyway, the kinds of transitivity we're discussing are:

   1. interface inheritance transitivity (i.e. adapt(x,IBase) if 
adapt(x,IDerived) and IDerived inherits from IBase)

   2. adapter composition transitivity (i.e. adapt(x,ISome) if 
adapt(x,IOther) and there is a general-purpose ISome->IOther adapter available.

These are mostly issues for the design of an interface system (implementing 
__adapt__) and for the design of a global adapter registry.  I don't think 
it's practical to implement either kind of transitivity on the __conform__ 
side, at least not for hand-written __conform__ methods.

To summarize current PEP 246 implementations' choices on this issue, Zope 
implements type 1 transitivity, but not type 2; PyProtocols implements 
both.  Both Zope and PyProtocols allow for individual objects to assert 
compliance with an interface that their class does not claim compliance 
with, and to use this assertion as a basis for adaptation.

In the case of PyProtocols, this is handled by adding a per-instance 
__conform__, but Zope has a separate concept of declaring what interfaces 
an instance "provides", distinct from what it is "adaptable 
to".  PyProtocols in contrast considers "provides" to be the same as 
"adaptable to with no adapter", i.e. a trivial special case of adaptation 
rather than a distinct concept.

I have also asserted that in practice I have encountered more problems with 
type 1 transitivity than with type 2, because of the strong temptation to 
derive an interface to avoid duplicating methods.  In other words, 
inappropriate use of interface inheritance produces roughly the same effect 
as introducing a noisy adapter into a type 2 adapter mesh, but IMO it's 
much easier to do innocently and accidentally, as it doesn't even require 
that you write an adapter!



More information about the Python-Dev mailing list