Python元类、monkey patch以及类装饰器

本篇简单记录一些关于Python如何利用元类、monkey patch以及类装饰器来动态修改类的知识。

Python元类

基本观点

这些观点主要来自What are metaclasses in Python?

  • type是Python的内建元类;

  • __metaclass__实际上可以被任意调用,它并不需要是一个正式的类;

  • 当使用class关键字时,Python解释器自动创建类对象;

  • 如果不希望自动创建,可以采用type,type能动态的创建类。它接受类的描述作为参数,然后返回一个类;

  • 创建类会经历的查找过程:编写的类中查找有__metaclass__,没有就继续在父类找,没有就在模块层次找,最终都没找到,就用内置的type创建类对象;

  • __metaclass__可以放置type,或者任何使用到type或者子类化type的东东都可以;

  • 元类的主要目的就是为了创建类时能够自动地改变类,具体实现为:(1)拦截类的创建,(2)修改类,(3)返回修改后的类;

  • __new 是在init__之前被调用的特殊方法;

  • __new__是用来创建对象并返回之的方法;

  • \而init只是用来将传入的参数初始化给对象;

  • type.__new__创建type对象并返回;

代码实践

type创建类对象的基本格式:type(类名, 父类的元组(针对继承的情况,可以为空),包含属性的字典(名称和值))

下面是一个元编程的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
>>> def upper_attr(future_class_name, future_class_parents, future_class_attr):
... attrs = ((name, value) for name, value in future_class_attr.items() if not name.startswith('__'))
... uppercase_attr = dict((name.upper(), value) for name, value in attrs)
... return type(future_class_name, future_class_parents, uppercase_attr)
...
>>> class Foo(object):
... bar = 'bip'
... __metaclass__ = upper_attr
... chen = 'cg'
...
>>> f = Fool()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'Fool' is not defined
>>> f = Foo()
>>> print f.chen
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'Foo' object has no attribute 'chen'
>>> print f.CHEN
cg
>>> print f.bar
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'Foo' object has no attribute 'bar'
>>> print f.BAR
bip
>>>

__metaclass__所处的位置不影响构建类对象;

monkey patch

monkey patch:概念上类似于Java里面的热修复,主要作用是在不更改源代码的情况下,动态追加和变更类的功能;

一个简单的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
>>> class Moneky(object):
... def eat(self):
... print "i want to eat banana"
...
>>> m = Moneky()
>>> m.eat()
i want to eat banana
>>> def common_eat(self):
... print "sorry"
...
>>> Moneky.eat = common_eat
>>> m.eat()
sorry

eat方法在运行时成功替换成common_eat方法。

类装饰器

首先明确functools包下的wraps,它可以使函数在调用namedoc属性时,打印被装饰的函数的信息,而非装饰函数的信息。

写一个类装饰器的例子,下面是写法一:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 写法一
from functools import wraps
class A():
def route(self, func):
@wraps(func)
def wrapper(*arg, **args):
self.printA()
return func(*arg, **args)
return wrapper
def printA(self):
print "hello"
app = A()
@app.route # 不写括号传参
def hello():
'''hellodoc'''
print "I am hello"
if __name__ == '__main__':
hello()
print(hello.__doc__)

route参数只有一个且就是被装饰的函数时(这里指不包含self),可以直接不写括号;

在看另一种写法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 写法二
class A():
def route(self, str):
def wrapper(func):
self.printA()
return func # 返回的不再是函数求得的值,而是函数对象
return wrapper
def printA(self):
print "hello"
app = A()
@app.route("/")
def hello():
'''hellodoc'''
print "I am hello"
if __name__ == '__main__':
hello()
print(hello.__doc__)

这里的route设定的是要传入一个str参数,在书写装饰器时显式给出,在构造装饰器逻辑的时候,嵌套的方法wrapper被返回的是func函数对象而不是函数运算求得的值,所以只是函数的引用改变了,那么打印函数的doc仍是原函数的内容;

类装饰器有一个好处是装饰的逻辑可以通过类继承,这样能方便程序的扩展;

参考

What are metaclasses in Python?

What is monkey patching?

深刻理解Python中的元类(metaclass)