Ian Bicking: the old part of his blog

A Conservative Metaclass

I use metaclasses in several of the projects I've written. In just about all of the cases, the metaclasses have been very minimal (or at least would be if I was clear ahead of time what I was doing), but all metaclasses have an air of mystery about them.

The metaclasses I've used have been primarily to support a sort of declarative style of programming. For instance, consider a validation schema:

class Registration(schema.Schema):
    first_name = validators.String(notEmpty=True)
    last_name = validators.String(notEmpty=True)
    mi = validators.MaxLength(1)
    class Numbers(foreach.ForEach):
        class Number(schema.Schema):
            type = validators.OneOf(['home', 'work'])
            phone_number = validators.PhoneNumber()
Hopefully you can imagine what that schema means from the class statement, even if you wouldn't know exactly how to use it. In another system this might look like:
registration = schema.Schema()
    'first_name', validators.String(not_empty=True))
optparse is an example of this style.

Anyway, while metaclasses are useful here, their use is very minimal. This is a metaclass you could use for something like this:

class DeclarativeMeta(type):
    def __new__(meta, class_name, bases, new_attrs):
        cls = type.__new__(meta, class_name, bases, new_attrs)
        cls.__classinit__.im_func(cls, new_attrs)
        return cls
class Declarative(object):
    __metaclass__ = DeclarativeMeta
    def __classinit__(cls, new_attrs): pass
The basic idea is just to run a function (__classinit__) on the class when the class is created. For something like the schema, I then go through the new attributes and see if any of them are "magic", like:
class Schema(Declarative):
    fields = {}
    def __classinit__(cls, new_attrs):
        cls.fields = cls.fields.copy()
        for name, value in new_attrs.items():
            if isinstance(value, validators.ValidatorBaseClass):
                cls.add_field(name, value)

    def add_field(cls, name, field):
        cls.fields[name] = field
        field.name = name

Basically I'm just indexing and naming the attributes in this case, which is actually all I'm likely to need to do. Also note that add_field allows me to continue to modify the class at runtime, which allows for all sorts of metaprogramming down the road. In SQLObject I use this when determining columns from the database, then adding the column objects using this class method.

The end result is something that I find aesthetically pleasing to use, and avoids a lot of boilerplate. At the same time, I think it's fairly easy to understand, without getting caught up in the mysterious aspects of metaclasses.

Created 18 Nov '04
Modified 14 Dec '04