There are cases where the definition order makes a difference. For example, let's assume we are creating a class that represents a CSV (Comma Separated Values) format. The CSV format expects the fields to have a particular order. In some cases this will be indicated by a header but it's still useful to have a consistent field order. Similar systems are using in ORM systems such as SQLAlchemy to store the column order for table definitions and for the input field order within forms in Django.
An easy way to store the order of the fields is by giving the field instances a special __init__
method which increments for every definition, so the fields have an incrementing index property. This solution can be considered the classic solution as it also works in Python 2.
>>> import itertools >>> class Field(object): ... counter = itertools.count() ... ... def __init__(self, name=None): ... self.name = name ... self.index = next(Field.counter) ... ... def __repr__(self): ... return '<%s[%d] %s>' % ( ... self.__class__.__name__, ... self.index, ... self.name, ... ) >>> class FieldsMeta(type): ... def __new__(metaclass, name, bases, namespace): ... cls = type.__new__(metaclass, name, bases, namespace) ... fields = [] ... for k, v in namespace.items(): ... if isinstance(v, Field): ... fields.append(v) ... v.name = v.name or k ... ... cls.fields = sorted(fields, key=lambda f: f.index) ... return cls >>> class Fields(metaclass=FieldsMeta): ... spam = Field() ... eggs = Field() >>> Fields.fields [<Field[0] spam>, <Field[1] eggs>] >>> fields = Fields() >>> fields.eggs.index 1 >>> fields.spam.index 0 >>> fields.fields [<Field[0] spam>, <Field[1] eggs>]
For convenience, and to make things prettier, we have added the FieldsMeta
class. It is not strictly required here, but it automatically takes care of filling in the name if needed, and adds the fields
list which contains a sorted list of fields.
The previous solution is a bit more straightforward and supports Python 2 as well, but with Python 3 we have more options. As you have seen in the previous paragraphs, since Python 3 we have the __prepare__
method, which returns the namespace. From the previous chapters you might also remember collections.OrderedDict
, so let's see what happens when we combine them.
>>> import collections >>> class Field(object): ... def __init__(self, name=None): ... self.name = name ... ... def __repr__(self): ... return '<%s %s>' % ( ... self.__class__.__name__, ... self.name, ... ) >>> class FieldsMeta(type): ... @classmethod ... def __prepare__(metaclass, name, bases): ... return collections.OrderedDict() ... ... def __new__(metaclass, name, bases, namespace): ... cls = type.__new__(metaclass, name, bases, namespace) ... cls.fields = [] ... for k, v in namespace.items(): ... if isinstance(v, Field): ... cls.fields.append(v) ... v.name = v.name or k ... ... return cls >>> class Fields(metaclass=FieldsMeta): ... spam = Field() ... eggs = Field() >>> Fields.fields [<Field spam>, <Field eggs>] >>> fields = Fields() >>> fields.fields [<Field spam>, <Field eggs>]
As you can see, the fields are indeed in the order we defined them. Spam
first and eggs
after that. Since the class namespace is now a collections.OrderedDict
instance, we know that the order is guaranteed. Instead of the regular not predetermined order of the Python dict
. This demonstrates how convenient metaclasses can be to extend your classes in a generic way. Another big advantage of metaclasses, instead of a custom __init__
method, is that the users won't lose the functionality if they forget to call the parent __init__
method. The metaclass will always be executed, unless a different metaclass is added, that is.