[Python-Dev] proposal for interfaces

John Williams jrw@pobox.com
Fri, 27 Sep 2002 13:51:57 -0500


I have an idea for an interface mechnism for Python, and I'd like to see if
anyone likes it before writing an actual PEP.  The key features are:

- It's implementable in pure Python (I've already started working on it).
- The syntax to use it is fairly concise.
- Interfaces are inherited by default, but can be turned off.
- Classes are made to implement interfaces without altering the class
definition in any way.
- A class can support any number of interfaces, even multiple interfaces
that define methods with the same names.
- It's easily extensible to add new features in a backward-compatible way.
- It has support for design-by-contract idioms (this part is not essential
to the proposal, so I won't discuss it further here, but interfaces without
DBC seem kind of incomplete to me).

Basic Usage
===========

In actual practice it would look something like this:  Suppose you have a
class like this:

  class SomeClass:
    def foo(...): ...
    def bar(...): ...
    def foo2(...): ...

In the simplest case, suppose you have an interface Foo that defines a
single method, foo.  To declare that SomeClass implements foo, you'd say:

  Foo.bind(SomeClass)

Now, suppose you have a function that requires an argument implemeting
interface Foo.  You would probably code it like this:

  def foo_proc(foo_arg):
    foo_proxy = Foo(foo_arg)
    ...
    x = foo_proxy.foo(...)
    ...

Two things are happening here.  First, foo_arg is being checked to make sure
it implements Foo; if not, an InterfaceError will be raised.  Then,
foo_proxy becomes a proxy for foo_arg, but it *only* supports calling the
method foo, since that's all the interface defines.  (If foo_arg is already
a proxy object, the call the Foo will just return foo_arg.)

Defining an Interface
=====================

How is "Foo" defined?  It could look something like this:

  class Foo(interface):
    def doc_foo(self,...): "Docstring for foo method."

The "doc_" prefix on foo is not part of the method name; it is needed to
control how the interface treats the method.  If the method has default
behavior, you could say this instead:

  class Foo(interface):
    def default_foo(self,...):
      "Docstring for foo method."
      print "Defaults can be handy."

In the version, unlike the first, classes implemeting Foo need not define
their own "foo" method is the defualt will suffice.  Requiring some sort of
prefix attached to every name defined by the interface is a little ugly, but
it opens up a lot of possibilities for creating different behaviors with a
minimum of fuss--I have a lot of uses in mind for different prefixes that I
won't go into here.

Advanced Examples
=================

Let's say we have a new interface, FooBar, defined like this:

  class FooBar(interface):
    def doc_foo(...): "Method foo."
    def doc_bar(...): "Method quux."

And suppose we'd like to make SomeClass above implement FooBar, but we want
FooBar.foo to call SomeClass.foo2 instead of SomeClass.foo.  It's easy!

  FooBar.bind(SomeClass)
  FooBar[SomeClass].foo = "foo2" # Override default binding.

Now we can do really confusing stuff:

  x = SomeClass()
  Foo(x).foo()     # Calls x.foo()
  FooBar(x).foo()  # Calls x.foo2()

Of course you probably wouldn't do something so confusing on purpose, but it
could be useful when an object must support two different interfaces
(written by different people) that happen to have method names in common, or
to connect a class to an interface where the class defines all the needed
functionality but the methods have the wrong names.

For the last trick, let's imagine you want to derive a subclass of
SomeClass.  If you want the new class to inherit all the interfaces, do
nothing.  To remove an inteface, just do something like this:

  class AnotherClass(SomeClass): ...
  Foo.unbind(AnotherClass)

And it's done!

Please let me know if you like this idea (or hate it).  If I get a good
response I'll try to write a PEP this weekend and make the implementation
availble to try out.

--jw