预计阅读本页时间:-
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
广告:个人专属 VPN,独立 IP,无限流量,多机房切换,还可以屏蔽广告和恶意软件,每月最低仅 5 美元
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 at the end of the declaring class statement, and it returns the expected new class object. The function is simply catching the call that the type object’s __call__ normally intercepts by default:
making class
In MetaFunc:
...Spam
...(<class '__main__.Eggs'>,)
...{'__module__': '__main__', 'data': 1, 'meth': <function meth at 0x02B8B6A8>}
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
...(<class '__main__.Eggs'>,)
...{'__module__': '__main__', 'data': 1, 'meth': <function meth at 0x02B7BA98>}
In SubMeta.new:
...Spam
...(<class '__main__.Eggs'>,)
...{'__module__': '__main__', 'data': 1, 'meth': <function meth at 0x02B7BA98>}
In SubMeta init:
...Spam
...(<class '__main__.Eggs'>,)
...{'__module__': '__main__', 'data': 1, 'meth': <function meth at 0x02B7BA98>}
...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. However, we’ll see later that a simple function-based metaclass can often work much like a class decorator, which allows the metaclasses to manage instances as well as classes.