crazy use for python metaclasses
Saturday, December 19th, 2009i’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?




