Learning Python - Mark Lutz [570]
making class
In MetaFunc:
...Spam
...( ...{'__module__': '__main__', 'data': 1, 'meth': making instance data: 1 Overloading class creation calls with metaclasses Since they participate in normal OOP mechanics, it’s also possible for metaclasses to catch the creation call at the end of a class statement directly, by redefining the type object’s __call__. The required protocol is a bit involved, though: # __call__ can be redefined, metas can have metas class SuperMeta(type): def __call__(meta, classname, supers, classdict): print('In SuperMeta.call: ', classname, supers, classdict, sep='\n...') return type.__call__(meta, classname, supers, classdict) class SubMeta(type, metaclass=SuperMeta): def __new__(meta, classname, supers, classdict): print('In SubMeta.new: ', classname, supers, classdict, sep='\n...') return type.__new__(meta, classname, supers, classdict) def __init__(Class, classname, supers, classdict): print('In SubMeta init:', classname, supers, classdict, sep='\n...') print('...init class object:', list(Class.__dict__.keys())) class Eggs: pass print('making class') class Spam(Eggs, metaclass=SubMeta): data = 1 def meth(self, arg): pass print('making instance') X = Spam() print('data:', X.data) When this code is run, all three redefined methods run in turn. This is essentially what the type object does by default: making class In SuperMeta.call: ...Spam ...( ...{'__module__': '__main__', 'data': 1, 'meth': In SubMeta.new: ...Spam ...( ...{'__module__': '__main__', 'data': 1, 'meth': In SubMeta init: ...Spam ...( ...{'__module__': '__main__', 'data': 1, 'meth': ...init class object: ['__module__', 'data', 'meth', '__doc__'] making instance data: 1 Overloading class creation calls with normal classes The preceding example is complicated by the fact that metaclasses are used to create class objects, but don’t generate instances of themselves. Because of this, with metaclasses name lookup rules are somewhat different than what we are accustomed to. The __call__ method, for example, is looked up in the class of an object; for metaclasses, this means the metaclass of a metaclass. To use normal inheritance-based name lookup, we can achieve the same effect with normal classes and instances. The output of the following is the same as the preceding version, but note that __new__ and __init__ must have different names here, or else they will run when the SubMeta instance is created, not when it is later called as a metaclass: class SuperMeta: def __call__(self, classname, supers, classdict): print('In SuperMeta.call: ', classname, supers, classdict, sep='\n...') Class = self.__New__(classname, supers, classdict) self.__Init__(Class, classname, supers, classdict) return Class class SubMeta(SuperMeta): def __New__(self, classname, supers, classdict): print('In SubMeta.new: ', classname, supers, classdict, sep='\n...') return type(classname, supers, classdict) def __Init__(self, Class, classname, supers, classdict): print('In SubMeta init:', classname, supers, classdict, sep='\n...') print('...init class object:', list(Class.__dict__.keys())) class Eggs: pass print('making class') class Spam(Eggs, metaclass=SubMeta()): # Meta is normal class instance data = 1 # Called at end of statement def meth(self, arg): pass print('making instance') X = Spam() print('data:', X.data) Although these alternative forms work, most metaclasses get their work done by redefining the type superclass’s __new__ and __init__; in practice, this is usually as much control as is required, and it’s often simpler than other schemes.