python装饰器

最近在写一个脚本,使用到了decorator,记录一下。

问题

对于某些函数或方法,需要对其修改或增加相同的内容,或在调用时(调用前、调用后)执行相同的操作。常见的比如有下边的场景:

  • log功能: 需要在各函数调用时,向日志写入相关内容
  • 连接或登录状态的确认: 在执行某一函数之前,检查连接或登录状态,如不满足要求则转至登录窗口
  • 屏蔽字: 处理各系统聊天、留言、描述信息甚至起名时,检查是否包含屏蔽字,根据需要拒绝请求或替换为星号

类似于设计模式中的装饰器模式,python中的装饰器decorator就适用于这类场景,并可以简化代码,提高可读性和易维护性。

定义与使用

装饰器是一个用来修改函数、方法甚至类定义的可调用对象。装饰器会传入一个原始对象,并返回一个修改过的对象。可以在不对装饰的目标对象做任何代码变动的前提下增加额外功能。

在python中使用装饰器,在被装饰的对象之前使用@符号加装饰器对象的名称即可,如:

1
2
3
@dec
def func(arg1, arg2, ...):
pass

等同于:

1
2
3
def func(arg1, arg2, ...):
pass
func = dec(func)

举一个具体的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 定义装饰器
def log(func):
def wrapper(*args, **kw):
print('before call ' + func.__name__)
func(*args, **kw)
print('after call ' + func.__name__)
return
return wrapper
# 使用装饰器
@log
def sayhello():
print('hello world')
# 调用函数
sayhello()

将得到程序输出:

1
2
3
before call sayhello
hello world
after call sayhello

参数传入

装饰器可以传入参数,带参数的装饰器,形式如下:

1
2
3
@decomaker(argA, argB, ...)
def func(arg1, arg2, ...):
pass

等同于:

1
func = decomaker(argA, argB, ...)(func)

来一个具体的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 定义装饰器
def log(action):
def wrapper(func):
def inner_wrapper(*args, **kw):
print('before ' + action + ' ' + func.__name__)
func(*args, **kw)
print('after ' + action + ' ' + func.__name__)
return
return inner_wrapper
return wrapper
# 使用装饰器
@log("call")
def sayhello():
print('hello world')
# 调用函数
sayhello()

将得到程序输出:

1
2
3
before call sayhello
hello world
after call sayhello

嵌套

嵌套使用装饰器的语法如下:

1
2
3
4
@dec2
@dec1
def func(arg1, arg2, ...):
pass

等同于:

1
2
3
def func(arg1, arg2, ...):
pass
func = dec2(dec1(func))

来一个具体的例子:

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
# 定义装饰器
def outter_log(func):
def wrapper(*args, **kw):
print('outter before')
func(*args, **kw)
print('outter after')
return
return wrapper
# 定义装饰器
def inner_log(func):
def wrapper(*args, **kw):
print('inner before')
func(*args, **kw)
print('inner after')
return
return wrapper
# 使用装饰器
@outter_log
@inner_log
def sayhello():
print('hello world')
# 调用函数
sayhello()

将得到程序输出:

1
2
3
4
5
outter before
inner before
hello world
inner after
outter after

functools.wraps

注意使用装饰器时,实际上是使用一个新的函数取代了旧的函数,如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 定义装饰器
def dec(func):
def new_func(*args, **kw):
func(*args, **kw)
return
return new_func
# 使用装饰器
@dec
def old_func():
print('hello world')
# 调用函数
old_func()
print('this is ' + old_func.__name__)

最终会输出:

1
2
hello world
this is new_func

经过装饰的函数已不再是之前的函数,此时如果代码中使用__name__等来识别函数时将会失效,为避免这一问题,需要使用functools.wraps来将新函数的个属性赋值,使其与旧函数相同,对刚才的代码进行修改:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import functools
# 定义装饰器
def dec(func):
@functools.wraps(func) # 注意 要加上这一句!
def new_func(*args, **kw):
func(*args, **kw)
return
return new_func
# 使用装饰器
@dec
def old_func():
print('hello world')
# 调用函数
old_func()
print('this is ' + old_func.__name__)

此时程序会输出:

1
2
hello world
this is old_func

REFERENCE

https://www.python.org/dev/peps/pep-0318/

https://www.liaoxuefeng.com