|Ned Batchelder : Blog | Code | Text | Site|
A quest for pythonic interfaces
» Home : Text
Created 18 October 2004
As I said in Interfaces, the most important concept in designing software systems is the interface. Yet Python has no support for them. Here's a story about my quest for Pythonic interfaces.
I like interfaces. They let me create a concrete representation of the behavior of a class, separate from the implementation of that behavior. I also like Python, which has no language support for interfaces as such. I've been building a system in Python, and wanted to use interfaces as an organizing principle. I figured it would be simple to simulate interfaces in Python. They are just completely abstract base classes:
Now a class that implements the Executable interface simply inherits from Executable:
This works well, and serves some of the goals of interfaces:
While the simple Executable interface above will work, it has a problem: if an implementing class makes the mistake of not implementing a method, the interface's method will be used instead, and the interface's method silently does nothing. Leaving empty code paths is an invitation to mysterious bugs. This is a particular annoyance of mine: see Erroneously empty code paths for more on this.
To close that hole, I created a function to use as the body of interfaces' methods. If called, it will raise an exception:
We've used some helper methods here to get helpful reporting. Now if a class doesn't implement a method, an exception is raised with the name of the missing method:
But all this Python hacking to emulate Java interfaces may be missing the point: As I mentioned in Interfaces, Python doesn't have interfaces partly because interfaces are an artifact of staticly typed languages. Python's dynamic nature makes interfaces far less important. And interfaces don't have as much flexibility as Python offers in the first place.
As my system grew, I wanted to have an interface with many required methods, but then two methods, only one of which would ever be implemented by a base class:
Once we've done this, though, how do we tell which method to call? Because of the interface as a base class, there is no way to distinguish between a OneWay Executable and a TheOtherWay Executable. Both instances have both methods.
The statically typed way would be to extend Executable into the two interfaces we really want to discuss:
But now we're getting complicated, and if another method with two possibilities arises, we have to fracture our interfaces yet again. The extreme end of this scenario is many interfaces with very few methods each. This is the way some of the Java library is beginning to look.
The thing about dynamic languages like Python is that you don't have to limit yourself to declaring groups of methods via interfaces. Traditional object-oriented language give programmers two building blocks to choose from: inheritance (is-a) and delegation (has-a). Interfaces are a tool to use is-a for figuring out what an object can do. With Python, there's a better way to figure out what an object can do: you ask it. Introspection lets you ask about the availability of a method (lets call this does-a).
The Pythonic way to handle the dual-natured Executable is to ask the object if it can do executeOneWay, and if it can't, to do executeTheOtherWay:
But consider the interfaces I've implemented above. My fancy all-in-one interface has truly let me down: because my object derives from Executable, and Executable implements both executeOneWay and executeTheOtherWay, there's nothing a derived class can do to control its destiny. All Executable objects implement both methods, just not in a useful way. The stub exception-raising methods in the interface have neutralized the power of the Python "does-a" mechanism.
I could add methods to the interface to answer the question about the methods:
but now we're really duplicating the Pythonic hasattr approach, and building a whole parallel system for talking about methods.
The double ExecutableOneWay and ExecutableTheOtherWay interfaces could handle this split, but then I'm just moseying down the path to having an interface per method.
So I'm stuck: I don't know how I want to move forward. I like interfaces when they work well: a shorthand name for a whole set of behaviors. But once the behaviors become more complex, the interface shorthand breaks down.
Python makes it simple to ask about particular methods, but then fails me when I really do want to talk about a whole set of behaviors. There's a line here, with methods on one side, and interfaces on the other. Some problems are solved more naturally on one side, others on the other, but it doesn't feel line a smooth transition between the two.
I'll have to keep trying out possibilities, I guess.
Of course this topic has a long and distinguished history: