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): 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)
@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.