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() registration.add_field( '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): passThe 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) @classmethod 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.