[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