最近在写一个脚本,使用到了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
|
注意使用装饰器时,实际上是使用一个新的函数取代了旧的函数,如:
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