7-4-元类
1. 要么是元类,要么不是元类
元类主要是针对那些构建API和工具供他人使用的程序员。
1.1 提高魔力层次
元类允许我们在一条 class 语句的末尾,插入当创建一个类对象的时候自动运行的逻辑。这个逻辑不会把类名重新绑定到一个装饰器可调用对象,而是把类自身的创建指向特定的逻辑。
1.2 “辅助”函数的缺点
元类常常是可选的,我们通常可以通过管理器函数(有时候叫做“辅助”函数)传递类对象来实现同样的效果,这和我们通过管理器代码传递函数和实例来实现装饰器的目的很相似。
然而,元类:
- 提供一种更为正式和明确的结构。
- 有助于确保应用程序员不会忘记根据一个 API 需求来扩展他们的类。
- 通过把类定制逻辑工厂化到一个单独的位置(元类)中,避免代码冗余及其相关的维护成本。
2. 元类模型
2.1 类是 type 的实例
在 Python 3 中,用户定义的类对象是名为 type 的对象的实例, type 本身是一个类。
type 内置函数返回任何对象的类型(它本身是一个对象)。对于列表这样的内置类型,实例的类型是一个内置的列表类型,但是,列表类型的类型是类型 type 自身——顶层的 type 对象创建了具体的类型,具体的类型创建了实例。
type([]), type(type([])) # 列表实例由列表类创建,列表类由 type 类创建
(list, type)
type(list), type(type) # type 的类型是 type
(type, type)
实例创建自类,而类创建自 type。在 Python 3 中,类型与类的概念合并了 —— class 是 type,type 也是 class:
- 类型由派生自 type 的类定义。
- 用户定义的类是类型类的实例。
- 用户定义的类是产生它们自己的实例的类型。
class C: pass
X = C() # 类实例对象
type(X) # 实例是类的实例
__main__.C
X.__class__ # 实例的类
__main__.C
type(C) # 类是 type 的实例
type
C.__class__ # 类的类是 type
type
2.2 元类是 Type 的子类
由于类实际上是 type 类的实例,从 type 的定制的子类创建类允许我们实现各种定制的类。
- type 是产生用户定义的类的一个类。
- 元类是 type 类的一个子类。
- 类对象是 type 类的一个实例,或一个子类。
- 实例对象产生自一个类。
2.3 Class 语句协议
当 Python 遇到一条 class 语句,它会运行其嵌套的代码块以创建其属性——所有在嵌套代码块的顶层分配的名称都生成类对象中的属性。这些名称通常是嵌套的 def 所创建的方法函数,但是,它们也可以是分配来创建由所有实例共享的类数据的任意属性。
Python 遵从一个标准的协议来使这发生:在一条class语句的末尾,并且在运行了一个命名空间词典中的所有嵌套代码之后,它调用 type 对象来创建 class 对象:
class = type(classname, superclasses, attributedict)
type 对象反过来定义了一个 __call__
运算符重载方法,当调用 type 对象的时候,该方法运行两个其他的方法:
type.__new__(typeclass, classname, superclasses, attributedict)
type.__init__(class, classname, superclasses, attributedict)
__new__
方法创建并返回了新的 class 对象,并且随后 __init__
方法初始化了新创建的对象。这是 type 的元类子类通常用来定制类的钩子。例如:
class Eggs:
pass
class Span(Eggs): # 从 Eggs 继承
data = 1 # 类数据属性
def meth(self, arg): # 类方法属性
return self.data + arg
Python 将会从内部运行嵌套的代码块来创建该类的两个属性(data 和 meth),然后在 class 语句的末尾调用 type 对象,产生 class 对象:
Spam = type('Spam', (Eggs,), {'data': 1, 'meth': meth, '__module__': '__main__'})
你可以以这种方式调用 type
来动态创建类:
x = type('Spam', (), {'data': 1, 'meth': (lambda x, y: x.data + y)})
i = x()
x, i
(__main__.Spam, <__main__.Spam at 0x1b9dc0aaa90>)
i.data, i.meth(2)
(1, 3)
生成的类和运行 class
语句得到的类完全一样:
x.__bases__
(object,)
[(a, v) for (a, v) in x.__dict__.items() if not a.startswith('__')]
[('data', 1), ('meth', <function __main__.<lambda>(x, y)>)]
由于这个调用在 class 语句的末尾进行,它是用来扩展或处理一个类的、理想的钩子。技巧在于,用将要拦截这个调用的一个定制子类来替代 type。
3. 声明元类
在类标题中把想要的元类作为一个关键字参数列出来:
class Spam(metaclass=Meta):
继承超类也可以列在标题中,在元类之前。
class Spam(Eggs, metaclass=Meta):
当以这些方式声明的时候,创建类对象的调用在 class 语句的底部运行,修改为调用元类而不是默认的 type :
class = Meta(classname, superclasses, attributedict)
由于元类是 type 的一个子类,所以 type 类的 __call__
把创建和初始化新的类对象的调用委托给元类,如果它定义了这些方法的定制版本:
Meta.__new__(Meta, classname, superclasses, attributedict)
Meta.__init__(class, classname, superclasses, attributedict)
4. 编写元类
4.1 基本元类
最简单元类只是带有一个 __new__
方法的 type 的子类,该方法通过运行 type 中的默认版本来创建类对象。
class Meta(type):
def __new__(meta, classname, supers, classdict):
# 通过继承的 type.__call__ 调用
return type.__new__(meta, classname, supers, classdict)
由于元类在一条 class 语句的末尾调用,并且因为 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)
class Eggs:
pass
print('making class')
class Spam(Eggs, metaclass=MetaOne): # 继承自 Eggs,是 MetaOne 的实例
data = 1
def meth(self, arg):
return self.data + arg
print('making instance')
X = Spam()
print('data:', X.data, X.meth(2))
making class
In MetaOne.new:
...Spam
...(<class '__main__.Eggs'>,)
...{'__module__': '__main__', '__qualname__': 'Spam', 'data': 1, 'meth': <function Spam.meth at 0x0000027BA26AB6A8>}
making instance
data: 1 3
4.2 定制构建和初始化
元类也可以接入 __init__
协议,由 type 对象的 __call__
调用:通常,__new__
创建并返回类对象,__init__
初始化已经创建的类。
class MetaTwo(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=MetaTwo):
data = 1
def meth(self, arg):
return self.data + arg
print('making instance')
X = Spam()
print('data:', X.data, X.meth(2))
making class
In MetaOne.new:
...Spam
...(<class '__main__.Eggs'>,)
...{'__module__': '__main__', '__qualname__': 'Spam', 'data': 1, 'meth': <function Spam.meth at 0x0000027BA26AB950>}
In MetaOne.init:
...Spam
...(<class '__main__.Eggs'>,)
...{'__module__': '__main__', '__qualname__': 'Spam', 'data': 1, 'meth': <function Spam.meth at 0x0000027BA26AB950>}
...init class object: ['__module__', 'data', 'meth', '__doc__']
making instance
data: 1 3
类初始化方法在类构建方法之后运行,但是,两者都在 class 语句最后运行,并且在创建任何实例之前运行。Spam 中的 __init__
方法在实例创建时运行,且不会通过元类的 __init__
运行。
4.3 其他元类编程技巧
使用简单的工厂函数
元类不是真的需要是类,任何可调用对象都可以用作一个元类,只要它接收传递的参数并且返回与目标类兼容的一个对象。
# 一个简单的函数也可以像元类一样工作
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): # 在类最后运行简单函数
data = 1
def meth(self, arg):
return self.data + arg
print('making instance')
X = Spam()
print('data:', X.data, X.meth(2))
making class
In MetaFunc:
...Spam
...(<class '__main__.Eggs'>,)
...{'__module__': '__main__', '__qualname__': 'Spam', 'data': 1, 'meth': <function Spam.meth at 0x0000027BA26ABA60>}
making instance
data: 1 3
用常规类重载类创建调用
因为常规类实例可以响应操作符重载的调用操作,所以它们也可以在某些元类角色中发挥作用。下面的输出类似于以前的基于类的版本,但是它基于一个简单的类——一个根本不继承 type 的类,并且为它的实例提供一个 __call__
,该调用使用常规操作符重载捕获元类调用。
注意 __new__
和 __init__
必须有不同的名称,否则它们将在创建 Meta 实例时运行。
# 一个普通类实例可以作为元类
class MetaObj:
def __call__(self, classname, supers, classdict):
print('In MetaObj.call: ', classname, supers, classdict, sep='\n...')
Class = self.__New__(classname, supers, classdict)
self.__Init__(Class, classname, supers, classdict)
return Class
def __New__(self, classname, supers, classdict):
print('In MetaObj.new: ', classname, supers, classdict, sep='\n...')
return type(classname, supers, classdict)
def __Init__(self, Class, classname, supers, classdict):
print('In MetaObj.init: ', classname, supers, classdict, sep='\n...')
print('...init class object:', list(Class.__dict__.keys()))
class Eggs:
pass
print('making class')
class Spam(Eggs, metaclass=MetaObj()): # MetaObj 是普通类实例
data = 1
def meth(self, arg):
return self.data + arg
print('making instance')
X = Spam()
print('data:', X.data, X.meth(2))
making class
In MetaObj.call:
...Spam
...(<class '__main__.Eggs'>,)
...{'__module__': '__main__', '__qualname__': 'Spam', 'data': 1, 'meth': <function Spam.meth at 0x0000027BA26AB378>}
In MetaObj.new:
...Spam
...(<class '__main__.Eggs'>,)
...{'__module__': '__main__', '__qualname__': 'Spam', 'data': 1, 'meth': <function Spam.meth at 0x0000027BA26AB378>}
In MetaObj.init:
...Spam
...(<class '__main__.Eggs'>,)
...{'__module__': '__main__', '__qualname__': 'Spam', 'data': 1, 'meth': <function Spam.meth at 0x0000027BA26AB378>}
...init class object: ['__module__', 'data', 'meth', '__doc__']
making instance
data: 1 3
可以使用普通的超类继承来获取这个编码模型中的调用拦截器——这里的超类基本上与 type 的作用相同,至少在元类调度方面是这样的。
尽管这种替代形式有效,但是大多数元类通过重新定义 type 超类的 __new__
和 __init__
来完成它们的工作。
用元类重载类创建调用
由于元类参与普通的 OOP 机制,所以通过重新定义 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)
def __init__(Class, classname, supers, classdict):
print('In SuperMeta.init: ', classname, supers, classdict, sep='\n...')
print('...init class object:', list(Class.__dict__.keys()))
print('making metaclass')
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): # 通过 SuperMeta.__call__ 调用 SubMeta
data = 1
def meth(self, arg):
return self.data + arg
print('making instance')
X = Spam()
print('data:', X.data, X.meth(2))
making metaclass
In SuperMeta.init:
...SubMeta
...(<class 'type'>,)
...{'__module__': '__main__', '__qualname__': 'SubMeta', '__new__': <function SubMeta.__new__ at 0x0000027BA26AB7B8>, '__init__': <function SubMeta.__init__ at 0x0000027BA26C7048>}
...init class object: ['__module__', '__new__', '__init__', '__doc__']
making class
In SuperMeta.call:
...Spam
...(<class '__main__.Eggs'>,)
...{'__module__': '__main__', '__qualname__': 'Spam', 'data': 1, 'meth': <function Spam.meth at 0x0000027BA26C7840>}
In SubMeta.new:
...Spam
...(<class '__main__.Eggs'>,)
...{'__module__': '__main__', '__qualname__': 'Spam', 'data': 1, 'meth': <function Spam.meth at 0x0000027BA26C7840>}
In SubMeta.init:
...Spam
...(<class '__main__.Eggs'>,)
...{'__module__': '__main__', '__qualname__': 'Spam', 'data': 1, 'meth': <function Spam.meth at 0x0000027BA26C7840>}
...init class object: ['__module__', 'data', 'meth', '__doc__']
making instance
data: 1 3
5. 继承与实例的关系
- 元类继承自 type 类。 元类通常重新定义 type 类的
__new__
和__init__
,以定制类创建和初始化,但是,如果它们希望直接捕获类末尾的创建调用的话,它们也可以重新定义__call__
。 - 元类声明由子类继承。 metaclass=M 声明由该类的子类继承,因此,对于在超类链中继承了这一声明的每个类的构建,该元类都将运行。
- **元类属性没有由类实例继承。**由于类是元类的实例,所以元类中定义的行为应用于类,而不是类随后的实例。实例从它们的类和超类获取行为,但是,不是从任何元类获取行为。
- **元类属性通过类获取。**相反,类通过实例关系获取元类的方法。从技术上讲,通过类的
__class__
链接获取元类属性就像常规实例从他们的类获取名称,但继承首先通过__dict__
搜索尝试:在一个元类和一个超类中有相同名字可用时,使用超类(继承)版本而不是元类(实例)。但是,类的__class__
不用于它自己的实例:元类属性对它们的实例类可用,但对那些实例类的实例不可用。
例如:
# 文件 metainstance.py
class MetaOne(type):
def __new__(meta, classname, supers, classdict): # 重新定义 type 方法
print('In MetaOne.new: ', classname)
return type.__new__(meta, classname, supers, classdict)
def toast(self):
return 'toast'
class Super(metaclass=MetaOne): # 元类也通过 subs 继承
def spam(self): # MetaOne 运行两次
return 'spam'
class Sub(Super): # 超类:继承和实例
def eggs(self): # 类继承自超类
return 'eggs' # 但不继承自元类
In MetaOne.new: Super
In MetaOne.new: Sub
当这段代码运行的时候,元类处理两个客户类的构建,并且实例继承类属性而不是元类属性:
X = Sub() # 常规用户定义类的实例
X.eggs() # 从 Sub 继承
'eggs'
X.spam() # 从 Super 继承
'spam'
X.toast() # 不继承元类
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
<ipython-input-4-bf6b1eddb3ac> in <module>()
----> 1 X.toast() # 不继承元类
AttributeError: 'Sub' object has no attribute 'toast'
相反,类从超类继承名称,也获得元类的名称:
Sub.eggs(X) # 自己的方法
'eggs'
Sub.spam(X) # 继承自 Super
'spam'
Sub.toast() # 从元类获得
'toast'
Sub.toast(X) # 不是一个常规类方法
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-8-8c5cbd1d68c4> in <module>()
----> 1 Sub.toast(X) # 不是一个常规类方法
TypeError: toast() takes 1 positional argument but 2 were given
注意,当我们传入一个实例时,前面最后一个调用是如何失败的,因为该名称解析为一个元类方法,而不是一个普通的类方法。实际上,从对象获取名称及其源在这里都非常重要。从元类获取的方法绑定到主体类,而从常规类获取的方法如果通过类获取,则不绑定;如果通过实例获取,则绑定:
Sub.toast
<bound method MetaOne.toast of <class '__main__.Sub'>>
Sub.spam
<function __main__.Super.spam(self)>
X.spam
<bound method Super.spam of <__main__.Sub object at 0x0000023E37B57DA0>>
5.1 元类 VS 超类
A 元类的实例 B 可以获取 A 的属性,但是 B 自己的实例不能继承该属性。
class A(type): attr = 1
class B(metaclass=A): pass # B 是 meta 实例,获取 meta 属性
I = B() # I 继承类而不是元类
B.attr
1
I.attr
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
<ipython-input-2-3a1e9156cedd> in <module>()
----> 1 I.attr
AttributeError: 'B' object has no attribute 'attr'
如果 A 由元类转变成超类,则 B 的实例继承了超类 A 的属性。
class A: attr = 1
class B(A): pass # I 继承类和超类
I = B()
B.attr
1
I.attr
1
事实上,类通过 __class__
获取元类属性,以同样的方式,常规实例通过 __class__
也继承了。
I.__class__
__main__.B
I.__class__.attr
1
6. 元类方法
元类中的方法处理他们的实例 —— 类,不使用常规实例对象的 "self",而是类自身。
class A(type):
def x(cls): print('ax', cls) # A 元类
def y(cls): print('ay', cls) # y 被实例 B 重写
class B(metaclass=A):
def y(self): print('by', self) # A 常规类
def z(self): print('bz', self) # 命名空间字典有 y 和 z
B.x # 从元类获取 x
<bound method A.x of <class '__main__.B'>>
B.y # y 和 z 在类中定义
<function __main__.B.y(self)>
B.z
<function __main__.B.z(self)>
B.x() # 元类方法调用:获得 cls
ax <class '__main__.B'>
I = B() # 实例方法调用:获得 inst
I.y()
by <__main__.B object at 0x000001B4008A32B0>
I.z()
bz <__main__.B object at 0x000001B4008A32B0>
I.x() # 实例无法看见元类名称
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
<ipython-input-16-9a76b1cbc1f5> in <module>()
----> 1 I.x()
AttributeError: 'B' object has no attribute 'x'
6.1 元类方法 VS 类方法
虽然它们在继承可见性上有所不同,很像类方法,但是元类方法是用来管理类级数据的。元类方法只能通过类访问,并且不需要显式的 classmethod 类级数据声明来绑定类。换句话说,元类方法可以被认为是隐式类方法:
class A(type):
def a(cls): # 元类方法:获得类
cls.x = cls.y + cls.z
class B(metaclass=A):
y, z = 11, 22
@classmethod # 类方法:获得类
def b(cls):
return cls.x
B.a() # 调用元类方法,仅对类可见
B.x # 在 B 上创建类数据,通过常规实例获取
33
I = B()
I.x, I.y, I.z
(33, 11, 22)
I.b() # 类方法:发送类,而不是实例;对实例可见
33
I.a() # 仅可通过类访问元类方法
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
<ipython-input-21-8fe21644d0e1> in <module>()
----> 1 I.a()
AttributeError: 'B' object has no attribute 'a'
6.2 元类方法中的操作符重载
元类也可以使用操作符重载来使内置操作适用于它们的实例类。
class A(type):
def __getitem__(cls, i): # 用于处理类的元类方法
return cls.data[i]
class B(metaclass=A):
data = 'spam'
B[0] # 元类实例名称:仅对类可见
's'
B.__getitem__
<bound method A.__getitem__ of <class '__main__.B'>>
I = B()
I.data, B.data # 常规继承名称:对实例和类都可见
('spam', 'spam')
I[0] # 类实例不继承元类方法
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-25-0e54f79f1ac7> in <module>()
----> 1 I[0]
TypeError: 'B' object does not support indexing
7. 示例:向类添加方法
7.1 手动扩展
class Client1:
def __init__(self, value):
self.value = value
def spam(self):
return self.value * 2
class Client2:
value = 'ni?'
def eggsfunc(obj):
return obj.value * 4
def hamfunc(obj, value):
return value + 'ham'
Client1.eggs = eggsfunc
Client1.ham = hamfunc
Client2.eggs = eggsfunc
Client2.ham = hamfunc
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
方法总是在类创建之后分配给一个类,只要分配的方法是带有一个额外的第一个参数以接收主体 self 示例的函数,这个参数可以用来访问类实例中可用的状态信息,即便函数独立于类定义。
这种方法在独立的情况下工作得很好,并且可以在运行时任意地填充一个类。但它有一个潜在的主要缺点,对于需要这些方法的每个类,我们必须重复扩展代码。
7.2 基于元类的扩展
通过这种方式,我们避免了对任何给定的类修改扩展的机会。此外,在单独位置编写扩展更好地支持了未来的修改——集合中的所有类都将自动接收修改。
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
7.3 元类 VS 类装饰器:第二回合
类装饰器常常和元类在功能上有重合。
- 在新类创建后,在 class 语句的末尾,类装饰器把类名重新绑定到一个函数的结果。
- 元类通过在一条 class 语句的末尾把类对象创建过程路由到一个对象来工作。
基于装饰器的扩展
装饰器大致与元类的 __init__
方法对应。
def eggsfunc(obj):
return obj.value * 4
def hamfunc(obj, value):
return value + 'ham'
def Extender(aClass):
aClass.eggs = eggsfunc # 管理类,而不是实例
aClass.ham = hamfunc # 和元类 __init__ 相当
return aClass
@Extender
class Client1: # Client1 = Extender(Client1)
def __init__(self, value): # 在类声明最后重新绑定
self.value = value
def spam(self):
return self.value * 2
@Extender
class Client2:
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
管理实例而不是类
类装饰器常常可以和元类一样充当类管理角色。元类往往和装饰器一样充当实例管理的角色,但是,这更复杂一点。
- 类装饰器可以管理类和实例。
- 元类可以管理类和实例,但是管理实例需要一些额外工作。
def Tracer(aClass): # 在 @ 装饰时
class Wrapper:
def __init__(self, *args, **kargs): # 在实例创建时
self.wrapped = aClass(*args, **kargs)
def __getattr__(self, attrname):
print('Trace:', attrname)
return getattr(self.wrapped, attrname)
return Wrapper
@Tracer
class Person: # Person = Tracer(Person)
def __init__(self, name, hours, rate):
self.name = name
self.hours = hours
self.rate = rate
def pay(self):
return self.hours * self.rate
bob = Person('Bob', 40, 50) # bob 是一个 Wrapper
print(bob.name) # Wrapper 嵌入 Person
print(bob.pay()) # 触发 __getattr__
Trace: name
Bob
Trace: pay
2000
装饰器使用类名重新绑定来把实例对象包装到一个对象中,该对象在输出中给出跟踪行。
元类明确地设计来管理类对象创建,并且它们有一个为此目的而设计的接口。
def Tracer(classname, supers, classdict): # 类创建时
aClass = type(classname, supers, classdict) # 创建客户类
class Wrapper:
def __init__(self, *args, **kargs): # 在实例创建时
self.wrapped = aClass(*args, **kargs)
def __getattr__(self, attrname):
print('Trace:', attrname)
return getattr(self.wrapped, attrname)
return Wrapper
class Person(metaclass=Tracer): # 创建带 Tracer 的 Person
def __init__(self, name, hours, rate):
self.name = name
self.hours = hours
self.rate = rate
def pay(self):
return self.hours * self.rate
bob = Person('Bob', 40, 50) # bob 是一个 Wrapper
print(bob.name) # Wrapper 嵌入 Person“”
print(bob.pay()) # 触发 __getattr__
Trace: name
Bob
Trace: pay
2000
这依赖于两个技巧。首先,它必须使用一个简单的函数而不是一个类,因为 type 子类必须附加给对象创建协议。其次,必须通过手动调用 type 来手动创建主体类;它需要返回一个实例包装器,但是元类也负责创建和返回主体类。
8. 示例:对方法应用装饰器
8.1 用装饰器手动跟踪
def tracer(func): # 使用函数,而不是类的 __call__
calls = 0
def onCall(*args, **kwargs):
nonlocal calls
calls += 1
print('call %s to %s' % (calls, func.__name__))
return func(*args, **kwargs)
return onCall
import time
def timer(label='', trace=True):
def onDecorator(func):
def onCall(*args, **kargs):
start = time.clock()
result = func(*args, **kargs)
elapsed = time.clock() - start
onCall.alltime += elapsed
if trace:
format = '%s%s: %.5f, %.5f'
values = (label, func.__name__, elapsed, onCall.alltime)
print(format % values)
return result
onCall.alltime = 0
return onCall
return onDecorator
class Person:
@tracer
def __init__(self, name, pay):
self.name = name
self.pay = pay
@tracer
def giveRaise(self, percent): # giveRaise = tracer(giverRaise)
self.pay *= (1.0 + percent) # onCall 记住 giveRaise
@tracer
def lastName(self): # lastName = tracer(lastName)
return self.name.split()[-1]
bob = Person('Bob Smith', 50000)
sue = Person('Sue Jones', 100000)
print(bob.name, sue.name)
sue.giveRaise(.10) # 运行 onCall(sue, .10)
print('%.2f' % sue.pay)
print(bob.lastName(), sue.lastName()) # 运行 onCall(bob), 记住 lastName
call 1 to __init__
call 2 to __init__
Bob Smith Sue Jones
call 1 to giveRaise
110000.00
call 1 to lastName
call 2 to lastName
Smith Jones
8.2 用元类和装饰器跟踪
from types import FunctionType
class MetaTrace(type):
def __new__(meta, classname, supers, classdict):
for attr, attrval in classdict.items():
if type(attrval) is FunctionType: # 方法?
classdict[attr] = tracer(attrval) # 装饰
return type.__new__(meta, classname, supers, classdict) # 创建类
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('%.2f' % sue.pay)
print(bob.lastName(), sue.lastName())
call 1 to __init__
call 2 to __init__
Bob Smith Sue Jones
call 1 to giveRaise
110000.00
call 1 to lastName
call 2 to lastName
Smith Jones
在类创建的时候,元类自动把函数装饰器应用于每个方法,并且函数装饰器自动拦截方法调用,以便在此输出中打印出跟踪消息。
8.3 把任何装饰器应用于方法
from types import FunctionType
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)): # 应用装饰器于所有方法
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('%.2f' % sue.pay)
print(bob.lastName(), sue.lastName())
call 1 to __init__
call 2 to __init__
Bob Smith Sue Jones
call 1 to giveRaise
110000.00
call 1 to lastName
call 2 to lastName
Smith Jones
要对方法应用一种不同的装饰器,我们只要在类标题行替换装饰器名称。
class Person(metaclass=decorateAll(tracer)): # 应用 tracer
class Person(metaclass=decorateAll(timer())): # 应用计时器
class Person(metaclass=decorateAll(timer(label='**'))) # 装饰器参数
8.4 元类 VS 类装饰器:第三回合
它定义并使用一个类装饰器,该装饰器把一个函数装饰器应用于一个类的所有方法。
def decorateAll(decorator):
def DecoDecorate(aClass):
for attr, attrval in aClass.__dict__.items():
if type(attrval) is FunctionType:
setattr(aClass, attr, decorator(attrval)) # 不是 __dict__
return aClass
return DecoDecorate
@decorateAll(tracer) # 使用类装饰器
class Person: # 应用 func 装饰器到方法
def __init__(self, name, pay): # Person = decorateAll(..)(Person)
self.name = name # Person = DecoDecorate(Person)
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('%.2f' % sue.pay)
print(bob.lastName(), sue.lastName())
call 1 to __init__
call 2 to __init__
Bob Smith Sue Jones
call 1 to giveRaise
110000.00
call 1 to lastName
call 2 to lastName
Smith Jones