Finally understanding the appeal of 'Interfaces'

November 17, 2009

I spent a long time not really getting the need for coded, explicit implementations of 'Interfaces', by which I mean things like zope.interface. It didn't help that I generally encountered them as part of very large, complex systems like Zope and Twisted, and they tended to come with a lot of extra magic features, which made the whole idea seem like the sort of thing you only needed if you had to deal with such a beast.

Then, recently, the penny dropped and I finally saw the light. Shorn of complexity and extra features, what Interface implementations give you is an explicit and easily used way to assert and ask 'is-a' questions. Need to find out if this object is a compiled regular expression? Just ask if it supports the ICRegexp interface. What to be accepted as a compiled regular expression? Assert that you support ICRegexp.

(Assuming the best and forging ahead is still the most Pythonic approach, but per my original problem you sometimes do need to know this sort of thing. And per yesterday's entry, requiring inheritance is not the answer, especially if you want to build decoupled systems.)

When I put it this way, it's easy to see why you'd like a basic interface implementation. If you have to test at all, simple 'is-a' tests beat both 'is-a-descendant-of' restrictions and probing for duck typing with its annoyances and ambiguities (cf an earlier entry).

In this view, the important thing is really to have a unique name (really an object) for each interface, so that you avoid the duck typing ambiguity. A basic implementation is almost trivial; treat interfaces as opaque objects, and just register classes as supporting interfaces and then have an 'isinterface()' function that works by analogy to isinstance().

(This demonstrates the old computer science aphorism that there's no problem that can't be solved by an extra level of indirection, since that is basically what this does: it adds a level of indirection to isinstance(), so that instead of asking 'is this object an instance of one of these classes', you ask 'is this object an instance of a class that supports this interface'.)

More complex implementations are of course possible; you could give the interface objects actual behavior and information, add checks for basic duck typing compatibility with the interface, make it so that isinterface() can optionally check to see if the object seems to implement the interface without having declared it, and so on.

(Sooner or later you end up back at zope.interface.)


Comments on this page:

From 64.101.44.136 at 2009-11-18 15:17:42:

You are way over thinking this. Your original problem was that you wanted to check whether an object is a string or a regular expression.

def myfunc(regex):

   if isinstance(regex, basestring):
       regex = re.compile(obj)

   return re.match("some string")

Done!

This overloads on string, and assumes any other kind of object passed in is duck type compatible with the results of re.compile(obj).

Also, worrying about whether two different match functions are going to be semantically compatible is silly. If an class is string compatible, it will descend from basestring. If it doesn't, then it's a bug on the string implementation, not your code. If the caller passes in something weird that is neither a string nor a regex, then it is a bug in the caller.

By cks at 2009-11-18 16:40:56:

If an class is string compatible, it will descend from basestring.

I disagree very strongly. The essence of duck typing is that you can be completely compatible with X without anywhere near X's inheritance tree. If we are taking it seriously, we have to admit that you can be a string without being a basestring.

(basestring is actually an unusual case, because it is an abstract type with no behavior and is exposed by the C API, so you can actually write C-level extensions that inherit from it but completely do their own thing. By no means are all Python types this convenient.)

From 78.35.25.18 at 2009-11-18 18:13:57:

Shouldn’t that be hasinterface? (If not supportsinterface… but no one wants to type that much.)

Aristotle Pagaltzis

By cks at 2009-11-18 23:40:42:

I agree that hasinterface() is the better name. (I am not good at names for things.)

From 64.101.44.136 at 2009-11-20 16:14:29:

" me (brendan)-> If an class is string compatible, it will descend from basestring.

someone else's reply-> I disagree very strongly. The essence of duck typing is that you can be completely compatible with X without anywhere near X's inheritance tree. "

Yes... that is the "essence" of duck typing, but that's not how python does it... see basestring's documentation.

The post's author is talking about violating duck typing, so I was showing him how to do so in a minimalistic way.

His other idea of making all types implement interfaces, and doing explicit checks for interfaces would kill duck typing altogether.

Numbers and strings in python are already special and have abstract base classes designed for isinstance checks.

http://docs.python.org/library/numbers.html http://docs.python.org/library/functions.html see basestring here

Python supports duck typing, but it doesn't force you to use it. In practice, it's sometimes useful to do an explicit type check against strings or numbers in order to provide a simulation of function overloading.

In particular, I've seen a lot of libraries that offer functions that except either an object, or a string that can be used to construct a compatible object, as in my earlier example.

NOTE: earlier I constructred a regex with an argument of "obj" whereas that was a typo, and I meant to type "regex".

Written on 17 November 2009.
« 'Is-a' versus 'is-a-descendant-of'
Universities are open environments »

Page tools: View Source, View Normal, Add Comment.
Search:
Login: Password:
Atom Syndication: Recent Comments.

Last modified: Tue Nov 17 00:19:21 2009
This dinky wiki is brought to you by the Insane Hackers Guild, Python sub-branch.