[Python-3000] Special methods and interface-based type system

Phillip J. Eby pje at telecommunity.com
Thu Nov 23 07:01:18 CET 2006


At 08:29 PM 11/22/2006 -0800, Guido van Rossum wrote:
>One thing that rubs me the wrong way about generic functions is that
>it appears to go against OO. Now I'm not someone to take OO as
>religion, but there's something uncomfortable (for me) about how, in
>Phillip's world, many things become functions instead of methods,
>which brings along concerns about the global namespace filling up, and
>also about functionality being spread randomly across too many
>modules. I fear I will miss the class as a convenient focus for
>related functionality.

I originally proposed a solution for this back in January '05, but it was 
too premature.  But since you have now stated the problem that the proposal 
was intended to solve, perhaps the solution has a chance now.  :)

I will try to be as concrete as possible.  Let's start with an actual, 
hopefully non-exploding 'Interface' implementation, based on an assumption 
that we have generic functions available:

     class InterfaceClass(type):
         def __init__(cls, name, bases, cdict):
             for k,v in cdict.items():
                 # XXX this should probably skip at least __slots__,
                 #     __metaclass__, and __module__, but oh well
                 cdict[k] = AdaptingDescriptor(v)

     class Interface:
         __metaclass__ = InterfaceClass
         __slots__ = '__self__'

         def __init__(self, subject):
             # this isinstance() check should be replaced by an
             # 'unwrap()' generic function, so other adapter types
             # will work, but this is just an example, so...
             if isinstance(subject, Interface):
                 subject = subject.__self__
             self.__self__ = subject

     class AdaptingDescriptor:
         def __init__(self, descriptor):
             self.wrapped = descriptor
         def __get__(self, ob, typ=None):
             if ob is None:
                 return self
             return self.wrapped.__get__(ob.__self__, typ)

Now, using this new "interface framework", let's implement a small 
"mapping" typeclas... er, interface.

     class Mapping(Interface):
         def keys(self):
             return [k for k,v in self.items()]
         def items(self):
             return [k,self[k] for k in self.keys()]
         # ... other self-recursive definitions

What does this do?  Well, we can now call Mapping(foo) to turn an arbitrary 
object into something that has Mapping's generic functions as its methods, 
and invokes them on foo!  (I am assuming here that normal functions are 
implicitly overloadable, even if that means they change type at runtime to 
do so.)  We could even use interfaces for argument type declarations, to 
automatically put things in the "right namespace" for what the code expects 
to use.  That is, if you declare an argument to be a Mapping, then that's 
what you get.  If you call .keys() on the resulting adapted object and the 
type doesn't support the operation, you get an error.

Too late a form of error checking you say?  Well, make a more sophisticated 
factory mechanism in Interface.__new__ that actually creates (and caches) 
different adapter types based on the type of object being adapted, so that 
hasattr() tests will work on the wrapped type, or so that you can get an 
early error if none of the wrapped generic functions has a method defined 
for the target type.

A few important points here:

1. A basic interface mechanism is extemely simple to implement, given 
generic functions

2. It is highly customizable with respect to error checking and other 
features, even on a per-user basis, because there doesn't have to be only 
one "true" Interface type to rule them all (or one true generic function 
type either, but that's a separate discussion).

3. It allows interfaces to include partial implementations, ala Ping and 
Alex's past proposals, thus allowing you to implement partial mapping or 
"file" objects and have the rest of the interface's implementation filled 
in for you

4. It allows you to hide the very existence of the notion of a "generic 
function", if you prefer not to think about such things

5. It even supports interface inheritance and interface algebra: 
subclassing an interface allows adding new operations, and simple 
assignment suffices to compose new interfaces, e.g.:

     class MappingItems(Interface):
         items = Mapping.items

Notice that nothing special is required, this "just works" as a natural 
consequence of the rest of the implementation shown.

Okay, so now you want to know how to *implement* a "Mapping".  Well, 
simplest but most tedious, you can just register operations directly, e.g.:

      class MyMapping:
          def __init__(self, data):
              self.data = dict(data)
          defop operator.getitem(self, key):
              return self.data[key]
          defop Mapping.items(self):
              return self.data.items()

But as you can imagine, this would probably get a bit tedious if you're 
implementing lots of methods.  So, we can add metaclasses or class 
decorators here to say, "I implement these interfaces, so any methods I 
have whose names match the method names in the interfaces, please hook 'em 
up for me."  I'm going to leave out the implementation, as it should be a 
straightforward exercise for the reader to come up with many ways by which 
it can be accomplished.  The spelling might be something like:

     class MyMapping:
         implements(Mapping)

         def items(self):
             ...

         #etc.

At which point, we have now come full circle to being able to provide all 
of the features of interfaces, adaptation, and generic functions, without 
forcing anyone to give up the tasty OO flavor of method calls.  Heck, they 
can even keep the way they spell existing adaptation calls (e.g. IFoo(bar) 
to adapt bar to IFoo) in PEAK, Twisted, and Zope!

And finally, note that if you only want to perform one method call on a 
given object, you can also use the generics directly, e.g. 
Mapping.items(foo) instead of Mapping(foo).items().

Voila -- generic goodness and classic OO method-calling simplicity, all in 
one simple to implement package.  It should now be apparent why I said that 
interfaces are trivial to implement if you define them as namespaces for 
generic functions, rather than as namespaces for methods.

There are many spinoffs possible, too.  For example, you could have a 
factory function that turns an existing class's public operations into an 
interface object.  There are also probably also some dark corners of the 
idea that haven't been explored, because when I first proposed basically 
this idea in '05, nobody was ready for it.  Now maybe we can actually talk 
about the implications.



More information about the Python-3000 mailing list