python装饰器

Python includes a more natural way of decorating a function by using an annotation on the function that is decorated.
https://en.wikipedia.org/wiki/Decorator_pattern
译:Python提供了一种更为自然的方式来装饰一个函数,这种方法通过在被装饰的函数前加一个注释来时实现。

装饰器原理

看一个简单的例子:

1
2
3
4
5
6
7
8
9
10
11
12
def test(func):
def wrapper():
func()
print func.__name__, "test wrapper"
return wrapper

@test
def main():
print "start main"

main()
print main.__name__

得到的结果是

1
2
3
start main
main test wrapper
wrapper

可以看到装饰器会改变函数的属性

1
2
print main
<function wrapper at 0x7f79c2437938>

此时执行main函数已经被装饰器test返回的wrapper函数给替代了,即 main() == test()()
再看

1
2
3
4
5
6
7
8
9
def test(test_arg):
print test_arg
def wrapper(func):
print "inner_wrapper"
def inner_wrapper():
func()
print func.__name__, "test wrapper"
return inner_wrapper
return wrapper

  • 这时使用装饰器test必须给参数, 如果不给的话test_arg相当与main函数的地址,
  • 此时调用wrapper会报错(原因是参数不够)。
  • 如果去了wrapper函数的参数则inner_wrapper不会被调用
  • 可以看出带参数的装饰器其实包装了一层外壳,先执行最外层test函数的内容,
  • 然后将调用的函数的地址传入里层的装饰器

还有一个问题就是之前代码中的func.name和main.name的问题,
不难看出其实main函数的地址就是wrapper的地址,所以main.name打印的是wrapper函数的名字,
那么如何获得main函数真正的属性呢?

functools.wraps

1
2
3
4
5
6
def test(func):
@functools.wraps(func)
def wrapper():
func()
print func.__name__, "test wrapper"
return wrapper

加上functools.wraps装饰器后的打印结果如下:

1
2
3
start main
main test wrapper
main

进入functools中看warps函数的实现其实可以看到
functools.wraps函数将wrapper函数的属性使用setattr设置为传入的func即main函数的属性