Python中type的使用和元类的理解二
元类可以用来自动为类的所有方法添加装饰,把所有使用的类注册到一个API,自动为类添加用户接口逻辑,
这个逻辑不会把类名重新绑定到一个装饰器可调用对象,而是把类自身的创建指向特定的逻辑
一、Python 3.0中以及在Python 2.6的新式类的特性
- type是产生用户定义的类的一个类。
- 元类是type类的一个子类。
- 类对象是type类的一个实例,或一个子类。
- 实例对象产生字一个类。
二、Class语句协议
class创建一个类对象: 调用type对象来创建class对象: class A: pass -> class = type(classname, superclasses, attributedict) 初始化类方法: type.__new__(typeclass, classname, superclasses, attributedict) type.__init__(class, classname, superclasses, attributedict)
如下所示的类定义示例:
class Spam(Eggs): # Inherits from Eggs data = 1 # Class data attribute def meth(self, arg): # Class method attribute pass Python将会从内部运行嵌套的代码块来创建该类的两个属性(data和meth),然后在class语句的末尾调用type对象,产生class对象: Spam = type('Spam', (Eggs,), {'data': 1, 'meth': meth, '__module__': '__main__'})
三、元类初识,元类的定义
3.1 元类声明:
正如我们刚才看到的,类默认是type类创建的。要告诉Python用一个定制的元类来创建一个类,直接声明一个元类来拦截常规的类创建调用。怎么做到这点,依赖于你使用哪个Python版本。在Python 3.0中,在类标题中把想要的元类作为一个关键字参数列出来:
#声明元类
class Spam(metaclass=Meta):
3.2 超类继承:
继承超类也可以列在标题中,在元类之前。例如,在下面的代码中,新的类Spam继承自Eggs,但也是Meta的一个实例并且由Meta创建:
#继承超类 class Spam(Eggs, metaclass=Meta)
3.3 Mate的作用:
当以上这些方式声明的时候,创建类对象的调用在class语句的底部运行,修改为调用元类而不是默认的type:
class = Meta(classname, superclasses, attributedict) 由于元类是type的一个子类,所以type类的__call__把创建和初始化新的类对象的调用 委托给元类,如果它定义了这些方法的定制版本: Meta.__new__(Meta, classname, superclasses, attributedict) Meta.__init__(class, classname, superclasses, attributedict) 示例分析: class Spam(Eggs, metaclass=Meta) data = 1 def meth(self, arg): pass 在这条class语句的末尾,Python内部运行如下的代码来创建class对象: Spam = Meta('Spam', (Eggs,), {'data': 1, 'meth': meth, '__module__': '__main__'})
四、基本元类的创建
通过继承自type的__new__方法而运行。它通常执行所需的任何定制并且调用type的超类的__new__方法来创建并运行新的类对象:
class Meta(type): def __new__(meta, classname, supers, classdict): # Run by inherited type.__call__ return type.__new__(meta, classname, supers, classdict)
将元类接入元类钩子中以定制——由于元类在一条class语句的末尾调用,并且因为 type对象的__call__分派到了__new__和__init__方法
4.1 创建一个最基本的元类:
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(object): 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) 打印结果: making class In MetaOne.new: ...Spam ...(<class '__main__.Eggs'>,) ...{'__module__': '__main__', '__qualname__': 'Spam', 'data': 1, 'meth': <function Spam.meth at 0x000002B3585F3E18>} making instance data: 1
4.2 定制构建和初始化(__init__方法):
元类也可以接入__init__协议,由type对象的__call__调用:通常,__new__创建并返回了类对象,__init__初始化了已经创建的类。元类也可以用做在创建时管理类的钩子:
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) 打印结果: making class In MetaOne.new: ...Spam ...(<class '__main__.Eggs'>,) ...{'__module__': '__main__', '__qualname__': 'Spam', 'data': 1, 'meth': <function Spam.meth at 0x00000297605E3BF8>} In MetaOne init: ...Spam ...(<class '__main__.Eggs'>,) ...{'__module__': '__main__', '__qualname__': 'Spam', 'data': 1, 'meth': <function Spam.meth at 0x00000297605E3BF8>} ...init class object: ['__module__', 'data', 'meth', '__doc__'] making instance data: 1
五、用元类重载类创建调用(细谈元类创建)
由于它们涉及常规的OOP机制,所以对于元类来说,也可能直接在一条class语句的末尾捕获创建调用,通过定义type对象的__call__。然而,所需的协议有点多:
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) 当这段代码运行的时候,所有3个重新定义的方法都依次运行。这基本上就是type对象默认做的事情: 打印结果: 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
六、实例与继承的关系
1.元类继承自type类。尽管它们有一种特殊的角色元类,但元类是用c l a s s语句编写的,并且遵从Python中有用的OOP模型。例如,就像type的子类一样,它们可以重新定义type对象的方法,需要的时候重载或定制它们。元类通常重新定义type类的__new__和__init__,以定制类创建和初始化,但是,如果它们希望直接捕获类末尾的创建调用的话,它们也可以重新定义__c a l l__。尽管元类不常见,它们甚至是返回任意对象而不是type子类的简单函数。
2.元类声明由子类继承。在用户定义的类中,metaclass=M声明由该类的子类继承,因此,对于在超类链中继承了这一声明的每个类的构建,该元类都将运行。
3.元类属性没有由类实例继承。元类声明指定了一个实例关系,它和继承不同。由于类是元类的实例,所以元类中定义的行为应用于类,而不是类随后的实例。实例从它们的类和超类获取行为,但是,不是从任何元类获取行为。从技术上讲,实例属性查找通常只是搜索实例及其所有类的__dict__字典;元类不包含在实例查找中。
自定义实例化对象不能调用元类的属性与方法:
class MetaOne(type):
abc = 10 def __new__(meta, classname, supers, classdict): # Redefine type method print('In MetaOne.new:', classname) return type.__new__(meta, classname, supers, classdict) def toast(self): print('toast') class Super(metaclass=MetaOne): # Metaclass inherited by subs too def spam(self): # MetaOne run twice for two classes print('spam') class C(Super): # Superclass: inheritance versus instance def eggs(self): # Classes inherit from superclasses print('eggs') # But not from metclasses X = C() X.eggs() # Inherited from C X.spam() # Inherited from Super X.toast() # Not inherited from metaclass
print(X.abc) #Not inherited from attribute
当这段代码运行的时候,元类处理两个客户类的构建,并且实例继承类属性而不是元类属性: 打印结果: In MetaOne.new: Super In MetaOne.new: C eggs spam AttributeError: 'C' object has no attribute 'toast'
AttributeError: 'C' object has no attribute 'abc'
七、执行扩展的元类的静态工作实例
像装饰器一样,我们在元类中做扩展,声明了元类的每个类都将统一且正确地扩展,并自动地接收未来做出的任何修改。如下的代码展示了这一点:
这个示例中的元类仍然执行相当静态的工作:把两个已知的方法添加到声明了元类的每个类
def eggsfunc(obj): return obj.value * 4 def hamfunc(obj, value): return value + 'ham' class Extender(type): def __new__(meta, classname, supers, classdict): classdict['eggs'] = eggsfunc classdict['ham'] = hamfunc return type.__new__(meta, classname, supers, classdict) class Client1(metaclass=Extender): def __init__(self, value): self.value = value def spam(self): return self.value * 2 class Client2(metaclass=Extender): value = 'ni?' X = Client1('Ni!') print(X.spam()) print(X.eggs()) print(X.ham('bacon')) Y = Client2() print(Y.eggs()) print(Y.ham('bacon')) 打印结果: 两个客户类都使用新的方法扩展了,因为它们是执行扩展的元类的实例 Ni!Ni! Ni!Ni!Ni!Ni! baconham ni?ni?ni?ni? baconham
八、执行扩展的元类的动态工作实例
定制元类的动态行为:主体类也可以基于运行时的任意逻辑配置
class MetaExtend(type): def __new__(meta, classname, supers, classdict): if sometest(): classdict['eggs'] = eggsfunc1 else: classdict['eggs'] = eggsfunc2 if someothertest(): classdict['ham'] = hamfunc else: classdict['ham'] = lambda *args: 'Not supported' return type.__new__(meta, classname, supers, classdict)
九、基于类装饰器的扩展实例与元类管理的对比
类装饰器常常可以和元类一样充当类管理角色
- 类装饰器可以管理类和实例
- 元类可以管理类和实例,但是管理实例需要一些额外工作
def eggsfunc(obj): return obj.value * 4 def hamfunc(obj, value): return value + 'ham' def Extender(aClass): aClass.eggs = eggsfunc # Manages class, not instance aClass.ham = hamfunc # Equiv to metaclass __init__ return aClass @Extender class Client1: # Client1 = Extender(Client1) def __init__(self, value): # Rebound at end of class stmt self.value = value def spam(self): return self.value * 2 @Extender class Client2: value = 'ni?' X = Client1('Ni!') # X is a Client1 instance print(X.spam()) print(X.eggs()) print(X.ham('bacon')) Y = Client2() print(Y.eggs()) print(Y.ham('bacon')) 打印结果: Ni!Ni! Ni!Ni!Ni!Ni! baconham ni?ni?ni?ni? baconham
十、基于类装饰器的扩展实例与元类管理的对比2
10.1 类装饰器管理实例:
def Tracer(aClass): # On @ decorator class Wrapper: def __init__(self, *args, **kargs): # On instance creation self.wrapped = aClass(*args, **kargs) # Use enclosing scope name def __getattr__(self, attrname): print('Trace:', attrname) # Catches all but .wrapped return getattr(self.wrapped, attrname) # Delegate to wrapped object return Wrapper @Tracer class Person: # Person = Tracer(Person) def __init__(self, name, hours, rate): # Wrapper remembers Person self.name = name self.hours = hours self.rate = rate # In-method fetch not traced def pay(self): return self.hours * self.rate bob = Person('Bob', 40, 50) # bob is really a Wrapper print(bob.name) # Wrapper embeds a Person print(bob.pay())
打印结果:
Trace: name
Bob
Trace: pay
2000
10.2 元类管理实例:
def Tracer(classname, supers, classdict): # On class creation call aClass = type(classname, supers, classdict) # Make client class class Wrapper: def __init__(self, *args, **kargs): # On instance creation self.wrapped = aClass(*args, **kargs) def __getattr__(self, attrname): print('Trace:', attrname) # Catches all but .wrapped return getattr(self.wrapped, attrname) # Delegate to wrapped object return Wrapper class Person(metaclass=Tracer): # Make Person with Tracer def __init__(self, name, hours, rate): # Wrapper remembers Person self.name = name self.hours = hours self.rate = rate # In-method fetch not traced def pay(self): return self.hours * self.rate bob = Person('Bob', 40, 50) # bob is really a Wrapper print(bob.name) # Wrapper embeds a Person print(bob.pay()) 打印结果: Trace: name Bob Trace: pay 2000
十一、基于类装饰器的扩展实例与元类管理的对比3
装饰器弊端:我们在想要在类中跟踪的每个方法前面添加装饰语法,并且在不再想要跟踪的使用后删除该语法。如果想要跟踪一个类的每个方法,在较大的程序中,这会变得很繁琐
元类创建跟踪方法:在构建一个类的时候运行,它们是把装饰包装器添加到一个类方法中的自然地方。通过扫描类的属性字典并测试函数对象,我们可以通过装饰器自动运行方法,并且把最初的名称重新绑定到结果。其效果与装饰器的自动方法名重新绑定是相同的。
class MetaTrace(type): def __new__(meta, classname, supers, classdict): for attr, attrval in classdict.items(): if type(attrval) is FunctionType: # Method? classdict[attr] = tracer(attrval) # Decorate it return type.__new__(meta, classname, supers, classdict) # Make class class Person(metaclass=MetaTrace): def __init__(self, name, pay): self.name = name self.pay = pay def giveRaise(self, percent): self.pay *= (1.0 + percent) def lastName(self): return self.name.split()[-1] bob = Person('Bob Smith', 50000) sue = Person('Sue Jones', 100000) print(bob.name, sue.name) sue.giveRaise(.10) print(sue.pay) print(bob.lastName(), sue.lastName())
打印结果: call 1 to __init__ call 2 to __init__ Bob Smith Sue Jones call 1 to giveRaise 110000.0 call 1 to lastName call 2 to lastName Smith Jones
要对方法应用一种不同的装饰器,我们只要在类标题行替换装饰器名称,实用与多种装饰的方法:
from types import FunctionType from mytools import tracer, timer def decorateAll(decorator): class MetaDecorate(type): def __new__(meta, classname, supers, classdict): for attr, attrval in classdict.items(): if type(attrval) is FunctionType: classdict[attr] = decorator(attrval) return type.__new__(meta, classname, supers, classdict) return MetaDecorate class Person(metaclass=decorateAll(tracer)): # Apply a decorator to all def __init__(self, name, pay): self.name = name self.pay = pay def giveRaise(self, percent): self.pay *= (1.0 + percent) def lastName(self): return self.name.split()[-1] bob = Person('Bob Smith', 50000) sue = Person('Sue Jones', 100000) print(bob.name, sue.name) sue.giveRaise(.10) print(sue.pay) print(bob.lastName(), sue.lastName()) 打印结果: call 1 to __init__ call 2 to __init__ Bob Smith Sue Jones call 1 to giveRaise 110000.0 call 1 to lastName call 2 to lastName Smith Jones
第一个接收了定时器的默认参数,第二个指定了标签文本:
class Person(metaclass=decorateAll(tracer)): # Apply tracer class Person(metaclass=decorateAll(timer())): # Apply timer, defaults class Person(metaclass=decorateAll(timer(label='**'))): # Decorator arguments
总结:
元类和类装饰器不仅常常可以交换,而且通常是互补的。它们都对于定制和管理类和实例对象,提供了高级但强大的方法,因为这二者最终都允许我们在类创建过程中插入代码。尽管某些高级应用可能用一种方式或另一种方式编码更好,但在很多情况下,我们选择或组合这两种工具的方法来使用。