python装饰器可以解决了在不修改函数代码的情况下,对函数功能拓展,但是之前的装饰器函数也存在一定的问题,还有多个装饰器同时装饰一个函数的情况。
0x00 返回原函数名称
之前的函数通过装饰器,可以实现了在使用原函数的func()的时候,通过@wrapper语法糖后,实现了功能的扩展,但是在调用一些类似__name__、__doc__函数的时候,返回的信息是inner()函数的信息。不过可以通过functools模块的wraps来实现完美的对函数进行装饰。
import time from functools import wraps # 新增功能 def timer(func): @wraps(func) def inner(a): start_time = time.time() ret = func(a) end_time = time.time() print(end_time - start_time) return ret return inner # 已有函数 @timer # 语法糖 def hw(a): time.sleep(5) print('Hello World!', a) return 'result' print(hw.__name__) #hw
这样通过在inner()的上方加上语法糖@wrap(),不过该处语法糖需要加入被装饰的函数名作为参数。这样在调用__name__、__doc__的时候,返回的信息就是原函数的了,而不是inner的。
0x01 带参数的装饰器
由于之前的装饰器函数中的参数为原函数名称,为了加入一个新的变量时候,可以通过闭包的特性来引入一个变量,不过这样装饰器就有3层函数了,当然一般情况下都不会弄那么多层嵌套。
import time from functools import wraps flag = True # 新增功能 def outter(flag): def timer(func): @wraps(func) def inner(a): if flag: start_time = time.time() ret = func(a) end_time = time.time() print(end_time - start_time) else: ret =func(a) return ret return inner return timer # 已有函数 @outter(flag) # 语法糖 def hw(a): # time.sleep(5) print('Hello World!', a) return 'result' hw(4) #flag = True,有新功能
当然一般这种情况基本不会出现,只是为了学习才。。。主要目的是为装饰器引入一个参数。其次在被装饰函数上方的语法糖也应该相应变为@outter(flag),不要忘记在语法糖中加入引入的参数。
0x02 多个装饰器装饰一个函数
对于多个装饰器装饰同一个函数的时候,由于变量与函数内存地址之间关系较为复杂,不过执行顺序较为有规律,可以只记忆执行顺序即可。
from functools import wraps def add_func1(func): @wraps(func) def inner(): print('func1') func() print('func1') return inner def add_func2(func): @wraps(func) def inner(): print('func2') func() print('func2') return inner @add_func2 @add_func1 def my_func(): print('This is function') my_func() ''' func2 func1 This is function func1 func2 '''
在内存中的加载顺序为先加载def add_func1(func),之后加载def add_func2(func),之后加载语法糖装饰器,顺序为@add_func1后才@add_func2,这里顺序比较奇怪,语法糖总是先加载离函数近的语法糖装饰器。
虽然变量的关系较为复杂,不过执行结果较为有规律。
- wrapper2(add_func2)中在被装饰函数之前的代码
- wrapper1(add_func1)中在被装饰函数之前的代码
- 被装饰函数代码
- wrapper1(add_func1)中在被装饰函数之后的代码
- wrapper2(add_func2)中在被装饰函数之后的代码