[Python-Dev] Son of PEP 246, redux

Phillip J. Eby pje at telecommunity.com
Thu Jan 13 07:04:01 CET 2005


At 12:01 AM 1/13/05 -0500, Michael Walter wrote:
>What am I missing?

The fact that this is a type-declaration issue, and has nothing to do with 
*how* types are checked.

Note that I'm only proposing:

1) a possible replacement for PEP 246 that leaves 'adapt()' as a function, 
but uses a different internal implementation,

2) a very specific notion of what an operation is, that doesn't require an 
interface to exist if there is already some concrete type that the 
interface would be an abstraction of,

3) a strawman syntax for declaring the relationship between operations

In other words, compared to the previous state of things, this should 
actually require *fewer* interfaces to accomplish the same use cases, and 
it doesn't require Python to have a built-in notion of "interface", because 
the primitive notion is an operation, not an interface.

Oh, and I think I've now figured out how to define a type-safe version of 
Ping's "abstract operations" concept that can play in the 
non-generic-function implementation, but I really need some sleep, so I 
might be hallucinating the solution.  :)

Anyway, so far it seems like it can all be done with a handful of decorators:

@implements(base_operation, for_type=None)
   (for_type is the "adapt *from*" type, defaulting to the enclosing class 
if used inside a class body)

@override
   (means the method is overriding the one in a base class, keeping the 
same operation correspondence(s) defined for the method in the base class)

@abstract(base_operation, *required_operations)
   (indicates that this implementation of base_operation requires the 
ability to use the specified required_operations on a target instance.  The 
adapter machinery can then "safely fail" if the operations aren't 
available, or if it detects a cycle between mutually-recursive abstract 
operations that don't have a non-abstract implementation.  An abstract 
method can be used to perform the operation on any object that provides the 
required operations, however.)

Anyway, from the information provided by these decorators, you can generate 
adapter classes for any operation-based interfaces.  I don't have a planned 
syntax or API for defining attribute correspondences as yet, but it should 
be possible to treat them internally as a get/set/del operation triplet, 
and then just wrap them in a descriptor on the adapter class.

By the way, saying "generate" makes it sound more complex than it is: just 
a subclass of 'object' with a single slot that points to the wrapped source 
object, and contains simple descriptors for each available operation of the 
"protocol" type that call the method implementations, passing in the 
wrapped object.  So really "generate" means, "populate a dictionary with 
descriptors and then call 'type(name,(object,),theDict)'".

A side effect of this approach, by the way, is that since adapters are 
*never* composed (transitively or otherwise), we can *always* get back to 
the "original" object.  So, in theory we could actually have 
'adapt(x,object)' always convert back to the original unwrapped object, if 
we needed it.  Likewise, adapting an already-adapted object can be safe 
because the adapter machinery knows when it's dealing with one of its own 
adapters, and unwrap it before rewrapping it with a new adapter.

Oh, btw, it should at least produce a warning to declare multiple 
implementations for the same operation and source type, if not an outright 
error.  Since there's no implicit transitivity in this system (either 
there's a registered implementation for something or there isn't), there's 
no other form of ambiguity besides dual declarations of a point-to-point 
adaptation.

Hm.  You know, this also solves the interface inheritance problem; under 
this scheme, if you inherit an operation from a base interface, it doesn't 
mean that you provide the base interface.

Oh, actually, you can still also do interface adaptation in a somewhat more 
restrictive form; you can declare abstract operations for the target 
interface in terms of operations in the base interface.  But it's much more 
controlled because you never stack adapters on adapters, and the system can 
tell at adaptation time what operations are and aren't actually available.

Even more interesting: Alex's "loss of middle name" example can't be 
recreated in this system as a problem, at least if I'm still thinking 
clearly.  But I'm probably not, so I'm going to bed now.  :)



More information about the Python-Dev mailing list