::crazy use for python metaclasses::

December 19th, 2009 by hamish download the zooToolBox

i’m not a programmer, so things that might be common place to the average programmer are new and exciting for me. when python was integrated into maya, I got the chance to see what a real programming language looked like, and its been super fun discovering all these amazing concepts, which leads me to metaclasses.

browsing around on the nets it seems metaclasses aren’t generally considered practical, so maybe i’m just using em wrong, but i’ve used them on a few occasions since i discovered them, and for what i consider to be super practical things.

for example, the rigging tools I’ve written at work use them.  the rigging tools define a collection of parts. these parts know how to manage themselves, from building to aligning to rigging. in code these parts are described as classes. now writing things like GUIs to leverage the functionality of the parts I wanted to make the GUI dynamic, so that I wouldn’t ever have to maintain it after the initial writing – I wanted it to just know what parts had been defined, and display UI for those parts as appropriate.

so this is where metaclasses come in handy.  normally when you define a class what you’re doing is creating an instance of a type object. remember classes in python are objects, and a class object is an instance of the type class. so by writing your own metaclass you can “hook” into the creation of class objects. in this case I wanted to keep track of subclasses of the base class as they got defined, without having to remember to register the classes somewhere. now technically this can be done in a variety of different ways, but this is by far the most elegant and simple solution that i’ve come up with.

so on to the code?

allClasses = []
class TrackableClass(type):
    def __new__( cls, name, bases, members ):
        newCls = type.__new__( cls, name, bases, members )
        allClasses.append( newCls )
        return newCls

class BaseClass(object):
    __metaclass__ = TrackableClass
    @classmethod
    def GetSubclasses( cls ):
        return [ c for c in allClasses if issubclass( c, cls ) ]

now you can call BaseClass.GetSubclasses() and it will return a list of all classes that inherit from BaseClass. this is interesting, but not super useful, because you have to re-define the above code everywhere you want to use it. so what we can do is wrap up the above code and build the metaclass and the GetSubclass method procedurally.

def trackableClassFactory( superClass=object ):
	'''
	returns a class that tracks subclasses.  for example, if you had classB(classA)
	ad you wanted to track subclasses, you could do this:

	class classB(trackableClassFactory( classA )): pass

	a classmethod called GetSubclasses is created in the returned class for
	querying the list of subclasses
	'''
	subclassList = []
	class TrackableType(type):
		def __new__( cls, name, bases, attrs ):
			new = type.__new__( cls, name, bases, attrs )
			subclassList.append( new )

			return new

	class TrackableClass(superClass): __metaclass__ = TrackableType
	def GetSubclasses( cls ):
		'''
		returns a list of subclasses
		'''
		toReturn = []
		for c in subclassList:
			if c is cls: continue
			if issubclass( c, cls ):
				toReturn.append( c )

		return toReturn
	def GetNamedSubclass( cls, name ):
		'''
		returns the first subclass found with the given name
		'''
		for c in cls.GetSubclasses():
			if c.__name__ == name: return c

	TrackableClass.GetSubclasses = classmethod( GetSubclasses )
	TrackableClass.GetNamedSubclass = classmethod( GetNamedSubclass )

	return TrackableClass

pretty simple hey. so now you can just insert the function call above into your class’s superclass definition like so:

class SomeClass(trackableClassFactory(SuperClass)): pass

and voila! you now have the GetSubclasses method available on the class, and can query all subclasses that have been defined! neato eh?


This entry was posted on Saturday, December 19th, 2009 at 11:02 and is filed under main, tutorials. 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.

Leave a Reply

CAPTCHA Image CAPTCHA Audio
Refresh Image