Learning Python - Mark Lutz [569]
Here’s our example in action again, with prints added to the metaclass and the file at large to trace:
class MetaOne(type):
def __new__(meta, classname, supers, classdict):
print('In MetaOne.new:', classname, supers, classdict, sep='\n...')
return type.__new__(meta, classname, supers, classdict)
class Eggs:
pass
print('making class')
class Spam(Eggs, metaclass=MetaOne): # Inherits from Eggs, instance of Meta
data = 1 # Class data attribute
def meth(self, arg): # Class method attribute
pass
print('making instance')
X = Spam()
print('data:', X.data)
Here, Spam inherits from Eggs and is an instance of MetaOne, but X is an instance of and inherits from Spam. When this code is run with Python 3.0, notice how the metaclass is invoked at the end of the class statement, before we ever make an instance—metaclasses are for processing classes, and classes are for processing instances:
making class
In MetaOne.new:
...Spam
...( ...{'__module__': '__main__', 'data': 1, 'meth': making instance data: 1 Customizing Construction and Initialization Metaclasses can also tap into the __init__ protocol invoked by the type object’s __call__: in general, __new__ creates and returns the class object, and __init__ initializes the already created class. Metaclasses can use both hooks to manage the class at creation time: class MetaOne(type): def __new__(meta, classname, supers, classdict): print('In MetaOne.new: ', classname, supers, classdict, sep='\n...') return type.__new__(meta, classname, supers, classdict) def __init__(Class, classname, supers, classdict): print('In MetaOne init:', classname, supers, classdict, sep='\n...') print('...init class object:', list(Class.__dict__.keys())) class Eggs: pass print('making class') class Spam(Eggs, metaclass=MetaOne): # Inherits from Eggs, instance of Meta data = 1 # Class data attribute def meth(self, arg): # Class method attribute pass print('making instance') X = Spam() print('data:', X.data) In this case, the class initialization method is run after the class construction method, but both run at the end of the class statement before any instances are made: making class In MetaOne.new: ...Spam ...( ...{'__module__': '__main__', 'data': 1, 'meth': In MetaOne init: ...Spam ...( ...{'__module__': '__main__', 'data': 1, 'meth': ...init class object: ['__module__', 'data', 'meth', '__doc__'] making instance data: 1 Other Metaclass Coding Techniques Although redefining the type superclass’s __new__ and __init__ methods is the most common way metaclasses insert logic into the class object creation process, other schemes are possible, too. Using simple factory functions For example, metaclasses need not really be classes at all. As we’ve learned, the class statement issues a simple call to create a class at the conclusion of its processing. Because of this, any callable object can in principle be used as a metaclass, provided it accepts the arguments passed and returns an object compatible with the intended class. In fact, a simple object factory function will serve just as well as a class: # A simple function can serve as a metaclass too def MetaFunc(classname, supers, classdict): print('In MetaFunc: ', classname, supers, classdict, sep='\n...') return type(classname, supers, classdict) class Eggs: pass print('making class') class Spam(Eggs, metaclass=MetaFunc): # Run simple function at end data = 1 # Function returns class def meth(self, args): pass print('making instance') X = Spam() print('data:', X.data) When run, the function is called