了解Python装饰器

概念了解

装饰器是一个Python函数,它可以让其他函数在不需要做任何代码变动的前提下增加额外功能,装饰器的返回值也是一个函数对象。

例如a()、b()等等函数都需要打印日志,为了减少“打印日志”部分代码的重复,就把这部分与函数功能本身无关的雷同代码抽离出来,并以装饰器的格式去重用它。

常见场景

插入日志、性能测试、事务处理、缓存、权限校验等等。

代码示例

如何理解Python装饰器?https://www.zhihu.com/question/26930016/answer/99243411

1
2
3
4
5
6
7
8
9
def func_a():
print('hello world')
logging.info("func_a is running")

def func_b():
a = 1
b = 2
print(a+b)
logging.info("func_b is running")

func_a()func_b()中都有打印日志的代码,如果之后还有函数func_cfunc_d也需要打印日志,这样就造成大量打印日志的雷同代码。
为了减少重复写代码,我们可以这样做,重新定义一个函数print_log专门处理打印日志的功能,并且在func_afunc_b中使用它。

1
2
3
4
5
6
7
8
def print_log(func):
logging.warn("%s is running" % func.__name__)
func()

def func_a():
print('hello world')

print_log(func_a)

但是这样的话,我们每次都要将一个函数作为参数传递给print_log函数。而且这种方式已经破坏了原有的代码逻辑结构,之前执行业务逻辑时,执行运行func_a(),但是现在不得不改成print_log(func_a)

  • 那么有没有更好的方式的呢?

    Python装饰器

1
2
3
4
5
6
7
8
9
10
11
12
# 这是一个简单的装饰器
def print_log(func):
def with_logging(*args, **kwargs):
logging.warn("%s is running" % func.__name__)
return func(*args, **kwargs)
return with_logging

def func_a():
print('hello world')

func_a = print_log(func_a)
func_a()

@符号是装饰器的语法糖,在定义函数的时候使用,避免再一次赋值操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 装饰器,应用 @ 语法糖写法
def print_log(func):
def with_logging(*args, **kwargs):
logging.warn("%s is running" % func.__name__)
return func(*args, **kwargs)
return with_logging

@print_log
def func_a():
print('hello world')

func_a()
# 这样可以省去func_a = print_log(func_a)这一句了
# 直接调用func_a()即可得到想要的结果。

装饰器丢失原函数信息

元信息丢失

使用装饰器极大地复用了代码,但是他有一个缺点就是原函数的元信息不见了,比如函数的docstring__name__参数列表,先看例子。

1
2
3
4
5
6
7
8
9
@print_log
def func_a():
print('hello world')

# 和以下代码效果一样
def func_a():
print('hello world')

func_a = print_log(func_a)

也就是说函数func_aprint_log所替代,即func_adocstring__name__等等信息都是with_logging函数的信息。

1
2
func_a.__name__ = 'with_logging'
# 原本应该等于 `func_a`

解决办法

使用functools.wraps,它能把原函数的元信息拷贝到装饰器函数中,这使得装饰器函数也有和原函数一样的元信息了。
(wraps也是一个装饰器)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from functools import wraps
def print_log(func):
@wraps(func)
def with_logging(*args, **kwargs):
logging.warn("%s is running" % func.__name__)
return func(*args, **kwargs)
return with_logging

@print_log
def func_a():
print('hello world')

func_a()
# 这样可以省去func_a = print_log(func_a)这一句了
# 直接调用func_a()即可得到想要的结果。