::Interface classes in python::

May 18th, 2011 by hamish download the zooToolBox

Python doesn’t really provide a defined way to implement interfaces. Python 2.6 provides abstract classes – although what I don’t like about this implementation is that you don’t get an exception until you try to instantiate the broken subclass, by which time it might be too late. At least, thats what it looks like from what I’ve read (for the most part I’m still stuck using python 2.5 – as I mainly work in maya 2009 so I haven’t bothered messing with any of this yet). I want a solution that happens at parse time – the equivalent to compile type for statically typed languages.

So I toyed around a bit this evening and came up with what you see below. It seems to work – at least for this trivial case. I’m not sure this is necessarily a good implementation, but it goes to show that its reasonably easy to do.

def interfaceFactory( metaclassSuper=type ):
	class AbstractClass(metaclassSuper):
		_METHODS_TO_IMPLEMENT = None

		def __new__( cls, name, bases, attrs ):
			subCls = metaclassSuper.__new__( cls, name, bases, attrs )

			#if this hasn't been defined, then cls must be the interface class
			if cls._METHODS_TO_IMPLEMENT is None:
				def _(): pass
				funcType = type( _ )
				cls._METHODS_TO_IMPLEMENT = methodsToImplement = []
				for name, obj in attrs.items():
					if type( obj ) is funcType:
						methodsToImplement.append( name )

			#otherwise it is a subclass that should be implementing the interface
			else:
				for methodName in cls._METHODS_TO_IMPLEMENT:
					if methodName not in attrs:
						raise TypeError( "The subclass %s doesn't implement the %s attribute!" % (name, methodName) )

			return subCls

	return AbstractClass

class TrackableType(type):
	_SUBCLASSES = []

	def __new__( cls, name, bases, attrs ):
		newCls = type.__new__( cls, name, bases, attrs )
		cls._SUBCLASSES.append( newCls )

		return newCls

class ISomething( metaclass=interfaceFactory( TrackableType ) ):
	def something( self ): pass
	def otherthing( self ): pass

class Something(ISomething):
	def something( self ): pass

If you run this code you’ll see a TypeError is raised complaining that the Something class doesn’t implement the otherthing method as soon as you import the script. Not when you try to instantiate a Something instance.

You’ll notice it only cares about methods being implemented – so if you wanted to force implementation of various properties in inherited classes, you’d probably need a few more conditionals in there – but I generally try to avoid properties. They feel dirty.

Anyway, nothing terribly useful, but I thought others might find it interesting.

Share

This post is public domain

This entry was posted on Wednesday, May 18th, 2011 at 21:41 and is filed under main. You can follow any responses to this entry through the RSS 2.0 feed. You can leave a response, or trackback from your own site.