In Python Metaclasses are considered an advanced and difficult to use technique. This is certainly true. Developers of ‘production code’ are often discouraged to use metaclasses, since code that makes use of metaclasses is often overly complex (especially for people who are not used to metaclasses).

A further reason to avoid metaclasses is that they truly can be avoided. They do not add much power to Python. However, there are cases when they make ‘client’ code more readable.

Consider this situation: we have a finite set of strings that are used as dictionary keys in several parts of our application. Of course, we don’t want to use explicit strings in every point where they are needed. This is too error prone and bugs are more difficult to track.

A good solution is to put all the strings in a module. We will have code such as

  FOO1 = 'foo1'
  FOO2 = 'foo2'

Good. However, we may want to loop over the constants. One of the more pythonic solutions is this one:

targets = {
    SEARCH        : search    ,
    INFO          : info      ,
    VARIANTS      : variants  ,
    DEPS          : deps      ,
    DEPENDENTS    : dependents,
    INSTALL       : install   ,
    UNINSTALL     : uninstall ,
    ACTIVATE      : activate  ,
    DEACTIVATE    : deactivate,
    INSTALLED     : installed ,
    LOCATION      : location  ,
    CONTENTS      : contents  ,
    PROVIDES      : provides  ,
    SYNC          : sync      ,
    OUTDATED      : outdated  ,
    UPGRADE       : upgrade   ,
    CLEAN         : clean     ,
    ECHO          : echo      ,
    LIST          : list      ,
    VERSION       : version   ,
    SELFUPDATE    : selfupdate,
    HELP          : help
}

locals().update(targets)

In this way we can do whatever we want. However, we could use a class instead of a module. We simply define the constants as class variables. Then we can easily use the locals built-in to create the array of constants. But with a little metaclass trick, we can write more interesting client code.

We could for example write things like:

        for k in PackageKey:
            # do something

or

        if key in PackageKey:
            return self.foo(key)

We can use metaclasses to define a class object that behaves in the way above.

class _ConstantNamespace(type):
    def __iter__(cls):
        for v in cls.keys:
            yield v

class PackageKey(object):
    __metaclass__ = _ConstantNamespace

    NAME                   = name
    VERSION                = version
    REVISION               = revision
    DIRECTORY              = directory
    VARIANTS               = variants
    HOMEPAGE               = homepage
    DESCRIPTION            = description
    BUILD_DEPENDENCIES     = build_dependencies
    LIBRARY_DEPENDENCIES   = library_dependencies
    RUNTIME_DEPENDENCIES   = runtime_dependencies
    PLATFORMS              = platforms
    MAINTAINERS            = maintainers
    keys = [ locals()[key] for key in locals().keys()
             if key.upper() == key and (key.isalpha() or key.endswith(_DEPENDENCIES))]