13-Python-装饰器
1、装饰器的定义
装饰器的本质就是函数,用来装饰其它函数,就是为其它函数添加附加功能。
装饰器原则如下:
- 不能修改被装饰的函数的源代码
- 不能修改被装饰的函数的调用方式
2、实现装饰器知识储备
- 函数即变量
1 def bar(): 2 print("in the bar") 3 def foo(): 4 print("in the foo") 5 bar() 6 7 foo() 8 9 print("----------分割线-----------") 10 11 def foo1(): 12 print("in the foo") 13 bar1() 14 def bar1(): 15 print("in the bar") 16 17 foo1() 18 19 print("----------分割线-----------") 20 # 这样会报错 21 # def foo2(): 22 # print("in the foo") 23 # bar2() 24 # foo2() 25 # def bar2(): 26 # print("in the bar")
- 高阶函数
- 把一个函数名当作实参传递给另外一个函数(在不修改被装饰函数源代码的情况下为其添加功能)
1 import time 2 3 def bar1(): 4 time.sleep(3) 5 print("in the bar") 6 7 def test2(func): 8 start_time = time.time() 9 func() # 相当于运行bar1() 10 stop_time = time.time() 11 print("total run time %s" %(stop_time - start_time)) 12 13 test2(bar1)
- 返回值中包含函数名(不能修改函数的调用方式)
1 def bar2(): 2 time.sleep(3) 3 print("in the bar2") 4 5 def test3(func): 6 print(func) 7 return func 8 9 print(test3(bar2)) # 获取的是内存地址 10 11 res = test3(bar2) 12 res()
- 嵌套函数
1 def foo(): 2 print("in the foo") 3 def bar(): 4 print("in the bar") 5 bar() # 局部变量只能在其作用域内调用 6 7 foo()
1 x = 0 2 def grandpa(): 3 x = 1 4 def dad(): 5 x = 2 6 def son(): 7 x = 3 8 print(x) # 最终打印结果为3 9 son() 10 dad() 11 grandpa()
- 高阶函数 + 嵌套函数 --》装饰器
1 import time 2 3 4 def timer(func): 5 def deco(): 6 start_time = time.time() 7 func() 8 stop_time = time.time() 9 print("total time is %s" % (stop_time - start_time)) 10 return deco # 返回deco()的内存地址 11 12 13 def test1(): 14 time.sleep(3) 15 print("in the test1") 16 17 18 def test2(): 19 time.sleep(3) 20 print("in the test2") 21 22 #以下可直接用装饰器语法代替 23 timer(test1) # test1的内存地址赋值给func,返回deco()的内存地址 24 print(timer(test1)) # 返回deco()的内存地址 25 test1 = timer(test1) # 内存地址赋值给test1 26 test1() # 相当于执行deco() 27 28 timer(test2) 29 test2 = timer(test2) 30 test2() 31 32 print("---------我是分隔符---------") 33 34 35 # 装饰器语法如下。(和上面引用的效果一样) 36 @timer # 相当于test1 = timer(test1) 37 def test1(): 38 time.sleep(3) 39 print("in the test1") 40 41 42 @timer # 相当于test1 = timer(test1) 43 def test2(): 44 time.sleep(3) 45 print("in the test2") 46 47 48 test1() 49 test2()
3、动态参数装饰器
def timer(bar): def inner(*args, **kwargs): start_time = time.time() foo_ret = bar(*args, **kwargs) end_time = time.time() used_time = end_time - start_time print(used_time) return foo_ret return inner @timer def foo(*args, **kwargs): time.sleep(1) print("我的参数:", args, kwargs) print("我的运行时间:") return "我的返回值" ret = foo("动态参数装饰器", (1, 2), name="Druid", age=18) print(ret)
输出结果如下:
4、装饰器原理图解
4.1 被装饰函数没有返回值
4.2 被装饰函数有返回值
注意:第二步仅为过程分析量,不作为真实的执行顺序。
5、装饰器固定格式
装饰器的固定格式如下例所示:
def wrapper(func): """ 该函数为装饰器函数 :param func: 这里的func参数实质是指向被装饰函数的内存地址 :return: """ def inner(*args, **kwargs): """ 该函数为装饰器函数内部函数 :param args: 实质接收的是被装饰函数的位置参数 :param kwargs: 实质接收的是被装饰函数的关键字参数 :return: 返回的是被装饰函数的返回值 """ print("这里放被装饰函数执行之前要做的事") func_ret = func(*args, **kwargs) # 被装饰的函数 print("这里放被装饰函数执行之后要做的事") return func_ret return inner @wrapper # 等价于my_func = wrapper(my_func) def my_func(*args, **kwargs): """ 该函数为被装饰函数 :param args: 接收位置参数 :param kwargs: 接收关键字参数 :return: 返回值 """ print(*args, **kwargs) return ret ret = my_func() # 执行原函数,实质是执行inner()。函数返回值保存在变量ret中。
6、装饰器修复
当我们使用装饰器去装饰某个函数时,我们想要引用被装饰函数原私有属性,如__name__、__doc__时,就有问题了,因为我们虽然仍然在执行被装饰函数,但其实执行的是闭包,看下例。
def wrapper(func): """ 该函数为装饰器函数 :param func: 这里的func参数实质是指向被装饰函数的内存地址 :return: """ def inner(*args, **kwargs): """ 该函数为闭包(装饰器函数内部函数) :param args: 实质接收的是被装饰函数的位置参数 :param kwargs: 实质接收的是被装饰函数的关键字参数 :return: 返回的是被装饰函数的返回值 """ func_ret = func(*args, **kwargs) # 被装饰的函数 return func_ret return inner @wrapper # 等价于my_func = wrapper(my_func) def my_func(*args, **kwargs): """ 该函数为被装饰函数 :param args: 接收位置参数 :param kwargs: 接收关键字参数 :return: 返回值 """ print(*args, **kwargs) return "返回值" my_func("装饰器没被修复前,被装饰函数原函数的私有属性如__name__、__doc__是获取不到的,如下:") # 执行原函数,实质是执行inner() print(my_func.__name__) # 打印函数的名字 print(my_func.__doc__) # 打印函数的注释文档
输出结果如下:
如果仍想使用被装饰函数的原私有属性,那么就可以用装饰器修复:
from functools import wraps def wrapper(func): """ 该函数为装饰器函数 :param func: 这里的func参数实质是指向被装饰函数的内存地址 :return: """ @wraps(func) def inner(*args, **kwargs): """ 该函数为闭包(装饰器函数内部函数) :param args: 实质接收的是被装饰函数的位置参数 :param kwargs: 实质接收的是被装饰函数的关键字参数 :return: 返回的是被装饰函数的返回值 """ func_ret = func(*args, **kwargs) # 被装饰的函数 print("装饰器修复不会改变原装饰器的作用") return func_ret return inner @wrapper # 等价于my_func = wrapper(my_func) def my_func(*args, **kwargs): """ 该函数为被装饰函数 :param args: 接收位置参数 :param kwargs: 接收关键字参数 :return: 返回值 """ print(*args, **kwargs) return "返回值" my_func("装饰器被修复后,被装饰函数原函数的私有属性如__name__、__doc__就可以正常获取了,如下:") # 执行原函数,实质是执行inner() print(my_func.__name__) # 打印函数的名字 print(my_func.__doc__) # 打印函数的注释文档
输出结果如下:
7、带参数的装饰器
需求:很多函数共用一个装饰器,要求随时可以关闭装饰器功能,且尽可能的减少代码修改。 该需求可以用标记位来实现,如下:
import time FLAG = True def timmer_out(flag): def timmer(func): def inner(*args, **kwargs): if flag: start_time = time.time() ret_func = func(*args, **kwargs) end_time = time.time() used_time = end_time - start_time print("函数{name}执行时间:{time}".format(name=func.__name__, time=used_time)) # print("函数{name}执行时间:{time}".format_map({"name": func.__name__, "time": used_time})) else: ret_func = func(*args, **kwargs) return ret_func return inner return timmer @timmer_out(FLAG) # 第一步,先执行timmer_out(FLAG),得到返回值timmer。第二步执行@timmer,即 my_func1 = timmer(my_func1) def my_func1(): time.sleep(1) print("my_func1") @timmer_out(FLAG) def my_func2(): time.sleep(1) print("my_func2") @timmer_out(FLAG) def my_func3(): time.sleep(1) print("my_func3") my_func1() my_func2() my_func3()
当FLAG置为True时,装饰器功能生效,输出结果如下图所示:
当FLAG置为False时,装饰器功能关闭,输出结果如下图所示:
8、多个装饰器装饰一个函数
def wrapper1(func): def inner1(): print("wrapper1, before func") func() print("wrapper1, after func") return inner1 def wrapper2(func): def inner2(): print("wrapper2, before func") func() print("wrapper2, after func") return inner2 @wrapper2 @wrapper1 def my_func(): print("function is my func") my_func()
注意输出结果:
为什么结果是这样?请看如下分析:
为什么是先执行@wrapper1而不是@wrapeer2呢?因为装饰器在找到被装饰函数会优先执行。