python 中的装饰器

很可惜,凭自己的三脚猫python功夫还不能够看懂Faster R-CNN源码。

先来学习一个python的装饰器写法。

Python中的装饰器是用了python的函数式特性和语法糖来实现的。简单来说,装饰器实现了在不修改底层代码(被调用方)和上层代码(调用方)的情况下,为被调用函数扩充功能。它可以被用于很多场景中,比如添加日志,权限控制等。首先讲函数装饰器。

函数装饰器

考虑下面的函数,如果我们要为它添加日志功能,通常需要这么写:

1
2
3
4
5
6
7
8
9
def foo():
print('i am foo')
def use_log(func):
# write some log to log file or console
print('%s is running'%func.__name__)
func()
# when you want to call foo() with log, do this
use_log(foo)

上面的写法要求修改所有的关于foo()的调用,如果还需要封装功能,修改会很繁琐。可以写成这样:

1
2
3
4
5
6
7
8
9
10
def use_log(func):
def wrapper():
# write some log to log file or console
print('%s is running'%func.__name__)
return func()
return wrapper
foo = use_log(foo)
# then just call foo will do
foo()

函数use_log就是装饰器,它把业务函数func包含在函数里面,看起来像foouse_log装饰了。使用python提供的装饰器语法糖@,在定义函数时使用,可以避免书写一次赋值操作。

1
2
3
4
5
@use_log
def bar():
print('i an bar')
# just call bar will do
bar()

对于@,假设有这样的代码:

1
2
3
@decorator(args)
def foo():
print('this is foo')

相当于foo = decorator(args)(foo)。所以在装饰器包含参数的时候,需要再封装一层函数,将真正的装饰器返回:

1
2
3
4
5
6
7
8
9
10
11
def use_log(caller):
def decorator(func):
def wrapper():
print('%s is calling %s'%(caller, func.__name__))
return func()
return wrapper
return decorator
@use_log('John Smith')
def foo():
print('this is foo')
foo()

如果被装饰的函数有参数,那么应当将形参填写到wrapper的形参中,并且返回时一并填上。

1
2
3
4
5
6
7
8
def use_log(func):
def wrapper(*args):
print('calling %s'%(func.__name__))
return func(args)
return wrapper
@use_log
def foo(x):
print('this is foo %d'%x)

但是如果我们此时运行print(foo.__name__)会发现函数原信息被丢弃了,变为了wrapper。使用python标准库提供的functools.wraps修饰器解决docstring__name__和参数列表丢失的问题。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import functools
def use_log(func):
@functools.wraps(func)
def wrapper():
print('calling %s'%(func.__name__))
return func()
return wrapper
@use_log
def foo():
"""test function"""
print('this is foo')
print(foo.__name__) # foo
print(foo.__doc__) # test function

当多个装饰器叠加的时候

1
2
3
4
5
6
7
@a
@b
@c
def f():
'''something you want to'''
# equals to
f = a(b(c(f)))

类装饰器

对拥有大量需要验证操作的gettersetter的类,逐个编写gettersetter会出现大量重复代码。在不改变类的设计的情况下,快速编写出这些函数,可以使用类装饰器。

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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
# you can google 'getattr() setattr() property()' to find usage of these functions
def ensure(name, validate, doc=None):
def decorator(Class):
privateName = '__' + name
def getter(self):
return getattr(self, privateName)
def setter(self, value):
validate(name, value)
setattr(self, privateName, value)
setattr(self, name, property(getter, setter, doc=doc))
return Class
return decorator
def is_non_empty_str(name, value):
if not isinstance(value, str):
raise ValueError('{} must be of type str'.format(name))
if not bool(value):
raise ValueError('{} may not be empty'.format(name))
def is_in_range(minimum = None, maximum = None):
assert minimum is not None or maximum is not None
def is_in_range(name, value):
if not isinstance(value, numbers.Number):
raise ValueError('{} must be a number'.format(name))
if minimum is not None and value < minimum:
raise ValueError('{} {} is too small'.format(name, value))
if maximum is not None and value > maximum:
raise ValueError('{} {} is too large'.format(name, value))
return is_in_range
def is_valid_isbn(name, value):
return is_non_empty_str(name, value)
@ensure('title', is_non_empty_str)
@ensure('isbn', is_valid_isbn)
@ensure('price', is_in_range(1, 10000))
@ensure('quantity', is_in_range(0, 1000000))
class Book:
def __init__(self, title, isbn, price, quantity):
self.title = title
self.isbn = isbn
self.price = price
self.quantity = quantity
@property
def value(self):
return self.price * self.quantity

类修饰器@ensure会为被修饰的类创建合适的验证器用于存取属性,而不需要很多代码。

Give it a try!