Say we want to be able to verify that classes we define always have an __info__()
method. We can accomplish that by defining a suitable metaclass and defining any new class that should be checked with a reference to this metaclass. Look at the following example code (also available as metaclassexample.py):
Chapter7/metaclassexample.py
class hasinfo(type): def __new__(metaclass, classname, baseclasses, classdict): if len(baseclasses) and not '__info__' in classdict: raise TypeError('does not have __info__') return type.__new__(metaclass, classname, baseclasses, classdict) class withinfo(metaclass=hasinfo): pass class correct(withinfo): def __info__(self): pass class incorrect(withinfo): pass
This will raise an exception for the incorrect
class, but not for the correct
class.
As you can see, the __new__()
method of a metaclass receives a number of important parameters. First, the metaclass itself and the classname
of the class that is being defined, a (possibly empty) list of baseclasses
, and finally the class dictionary. That last argument is very important.
As we define a class with a class statement, all methods and class variables we define here end up in a dictionary. Once this class is completely defined, this dictionary will be available as the __dict__
attribute of the class. This is the dictionary that is passed to the __new__()
method of the metaclass and we can examine and alter this dictionary at will.
In this example, we simply check whether there exists a key called __info__
in this class dictionary and raise an exception if it doesn't. (We do not really check that it is a method but this is possible too of course). If everything went well, we call the __new__()
method of type
(the mother of all metaclasses) as that method will take care of making the class definition available in the current scope.
There is an extra trick involved, however. The withinfo
class is abstract in the sense that it defines the need for an __info__()
method by referring to the hasinfo
metaclass, but it does not define one itself. However, because it refers to the hasinfo
metaclass, an exception would be raised because its own class dictionary is checked in the same way as its subclasses. To prevent this, we only check for the occurrence of the __info__()
method if a class is a subclass, that is, when the list of base classes (available in the baseclasses
parameter) is not empty.
Checking for mandatory methods is nice, but with so much information available, much more can be done. In the next section, we use this power to ensure that the definition of a new class will take care of creating suitable tables in a database backend as well.
Besides creating a database table, if necessary, the metaclass that we will define will also examine the Attribute
instance assigned to any class variable to build dictionaries of display names and validation functions. This way, subclasses can easily check if a column has such an attribute by using the column name as a key, thus obviating the need to check all class variables themselves again and again.
3.137.167.195