python装饰器

时间:2024-01-22 14:21:55

开闭原则:
在不修改原函数及其调用方式的情况下对原函数功能进行扩展
对代码的修改是封闭
不能修改被装饰的函数的源代码
不能修改被装饰的函数的调用方式

用函数的方式设想一下游戏里用枪的场景

复制代码
 1 def game():
 2     print('压子弹')
 3     print('枪上膛')
 4     print('发射子弹')
 5 game()
 6 game()
 7 game()
 8 
 9 此时需要给枪增加一个瞄准镜,比如狙击远程目标时候需要加,狙击近程目标不用加
10 此时上边的代码就变成了现在的代码
11 
12 def sight():
13     print('专业狙击瞄准镜')
14     game()
15 sight()
16 sight()
17 sight()
18 此时的设计就不符合开闭原则(因为修改了原代码及调用名称)
复制代码

装饰器(python里面的动态代理)
本质: 是一个闭包
组成: 函数+实参高阶函数+返回值高阶函数+嵌套函数+语法糖 = 装饰器
存在的意义: 在不破坏原有函数和原有函数调用的基础上,给函数添加新的功能

通用装饰器写法:

复制代码
 1 def warpper(fn):        # fn是目标函数相当于func
 2     def inner(*args,**kwargs):      # 为目标函数的传参
 3         '''在执行目标函数之前操作'''
 4         ret = fn(*args,**kwargs)    # 调用目标函数,ret是目标函数的返回值
 5         '''在执行目标函数之后操作'''
 6         return ret      # 把目标函数返回值返回,保证函数正常的结束
 7     return inner
 8 
 9 #语法糖
10 @warpper    #相当于func = warpper(func)
11 def func():
12     pass
13 func()      #此时就是执行的inner函数
复制代码

上边的场景用装饰器修改后

复制代码
 1 方式一
 2 def game():
 3     print('压子弹')
 4     print('枪上膛')
 5     print('发射子弹')
 6 
 7 def sight(fn):      # fn接收的是一个函数
 8     def inner():
 9         print('安装专业狙击瞄准镜')
10         fn()        #调用传递进来的函数
11         print('跑路')
12     return inner    #返回函数地址
13 
14 game = sight(game)  #传递game函数到sight函数中
15 game()
16 
17 执行步骤
18 第一步定义两个函数game()为普通函数,sight()为装饰器函数
19 第二步定义game = sight(game)等于把game函数当做参数传递给sight(fn)装饰器函数fn形参
20 第三步执行sight(fn),fn在形参位置,相当于下边函数game()传参过来等于fn
21 第四步执行inner函数,然后return把inner函数内存地址当做返回值返回给sight(game)
22 第五步然后执行game(),相当于执行inner函数
23 第六步,执行inner函数,打印'狙击镜',执行fn()形参,由于fn形参等于game函数,所以执行game()函数,打印'压子弹','上膛','发射子弹'
24 第七步打印'跑路'
25 第八步把打印的结果返回给game()
26 
27 方式二
28 def sight(fn):      # fn接收的是一个函数
29     def inner():
30         print('安装专业狙击瞄准镜')
31         fn()        #调用传递进来的函数
32         print('跑路')
33     return inner    #返回函数地址
34 
35 @sight      #相当于game = sight(game)
36 def game():
37     print('压子弹')
38     print('枪上膛')
39     print('发射子弹')
40 game()
41 
42 执行步骤
43 第一步执行sight(fn)函数
44 第二步执行@sight,相当于把把game函数与sight装饰器做关联
45 第三步把game函数当做参数传递给sight(fn)装饰器函数fn形参
46 第四步执行inner函数,然后return把inner函数内存地址当做返回值返回给@sight
47 第五步执行game()相当相当于执行inner()函数,因为@sight相当于game = sight(game)
48 第六步打印'瞄准镜
49 第七步执行fn函数,因为fn等于game函数,所以会执行game()函数,打印'压子弹','上膛','发射子弹'.fn()函数执行完毕
50 第八步打印'跑路'
51 第九步然后把所有打印的结果返回给game()
52 
53 结果
54 安装专业狙击瞄准镜
55 压子弹
56 枪上膛
57 发射子弹
58 跑路
复制代码

一个简单的装饰器实现

复制代码
 1 def warpper(fn):
 2     def inner():
 3         print('每次执行被装饰函数之前都要先经过这里')
 4         fn()
 5     return inner
 6 @warpper
 7 def func():
 8     print('执行了func函数')
 9 func()
10 
11 结果
12 每次执行被装饰函数之前都要先经过这里
13 执行了func函数
复制代码

 带有一个或多个参数的装饰器

复制代码
 1 def sight(fn):                      #fn等于调用game函数
 2     def inner(*args,**kwargs):      #接受到的是元组("bob",123)
 3         print('开始游戏')
 4         fn(*args,**kwargs)    #接受到的所有参数,打散传递给user,pwd
 5         print('跑路')
 6     return inner
 7 @sight
 8 def game(user,pwd):
 9     print('登陆游戏用户名密码:',user,pwd)
10     print('压子弹')
11     print('枪上膛')
12     print('发射子弹')
13 game('bob','123')
14 结果
15 开始游戏
16 登陆游戏用户名密码: bob 123
17 压子弹
18 枪上膛
19 发射子弹
20 跑路
复制代码

动态传递一个或多个参数给装饰器

复制代码
 1 def sight(fn):                      #调用game函数
 2     def inner(*args,**kwargs):      #接受到的是元组("bob",123)
 3         print('开始游戏')
 4         fn(*args,**kwargs)    #接受到的所有参数,打散传递给正常的参数
 5         print('跑路')
 6     return inner
 7 @sight
 8 def game(user,pwd):
 9     print('登陆游戏用户名密码:',user,pwd)
10     print('压子弹')
11     print('枪上膛')
12     print('发射子弹')
13     return '游戏展示完毕'
14 ret = game('bob','123')     #传递了两个参数给装饰器sight
15 print(ret)
16 
17 @sight
18 def car(qq):
19     print('登陆QQ号%s'%qq)
20     print('开始战车游戏')
21 ret2 = car(110110)          #传递了一个参数给装饰器sight
22 print(ret2)
23 结果
24 开始游戏
25 登陆游戏用户名密码: bob 123
26 压子弹
27 枪上膛
28 发射子弹
29 跑路
30 None
31 开始游戏
32 登陆QQ号110110
33 开始战车游戏
34 跑路
35 None
36 你会发现这两个函数执行的返回值都为None,但是我game定义返回值了return '游戏展示完毕',却没给返回
复制代码

装饰器的返回值

复制代码
 1 为什么我定义了返回值,但是返回值还是None呢,是因为我即使在game函数中定义了return '游戏展示完毕'
 2 但是装饰器里只有一个return inner定义返回值,但是这个返回值是返回的inner函数的内存地址的,并不是inner
 3 函数内部的return所以默认为None,所以应该定义一个inner函数内部的return返回值,而且也没有接收返回值的变量,
 4 所以要要设置ret = fn(*args,**kwargs)和return ret
 5 
 6 def sight(fn):                      #调用game函数
 7     def inner(*args,**kwargs):      #接受到的是元组("bob",123)
 8         print('开始游戏')
 9         ret = fn(*args,**kwargs)    #接受到的所有参数,打散传递给正常的参数
10         print('跑路')
11         return ret
12     return inner
13 @sight
14 def game(user,pwd):
15     print('登陆游戏用户名密码:',user,pwd)
16     print('压子弹')
17     print('枪上膛')
18     print('发射子弹')
19     return '游戏展示完毕'
20 ret = game('bob','123')     #传递了两个参数给装饰器sight
21 print(ret)
22 结果
23 开始游戏
24 登陆游戏用户名密码: bob 123
25 压子弹
26 枪上膛
27 发射子弹
28 跑路
29 游戏展示完毕
30 
31 
32 事例2
33 def wrapper_out(flag):      #装饰器本身的参数
34     def wrapper(fn):        #目标函数
35         def inner(*args,**kwargs):  #目标函数需要接受的参数
36             if flag == True:
37                 print('找第三方问问价格行情')
38                 ret = fn(*args,**kwargs)
39                 print('买到装备')
40                 return ret
41             else:
42                 ret = fn(*args,**kwargs)
43                 return ret
44         return inner
45     return wrapper
46 #语法糖,@装饰器
47 @wrapper_out(True)
48 def func(a,b):  #被wrapper装饰
49     print(a,b)
50     print('开黑')
51     return 'func返回值'
52 abc = func('我是参数1','我是参数2')
53 print(abc)
54 结果
55 找第三方问问价格行情
56 我是参数1 我是参数2
57 开黑
58 买到装备
59 func返回值
复制代码

多个装饰器同用一个函数

复制代码
 1 def wrapper1(fn):
 2     def inner(*args,**kwargs):
 3         print('wrapper1-1')
 4         ret = fn(*args,**kwargs)
 5         print('wrapper1-2')
 6         return ret
 7     return inner
 8 
 9 def wrapper2(fn):
10     def inner(*args,**kwargs):
11         print('wrapper2-1')
12         ret = fn(*args,**kwargs)
13         print('wrapper2-2')
14         return ret
15     return inner
16 
17 def wrapper3(fn):
18     def inner(*args,**kwargs):
19         print('wrapper3-1')
20         ret = fn(*args,**kwargs)
21         print('wrapper3-2')
22         return ret
23     return inner
24 @wrapper1
25 @wrapper2
26 @wrapper3
27 def func():
28     print('我是测试小白')
29 func()
30 结果
31 wrapper1-1
32 wrapper2-1
33 wrapper3-1
34 我是测试小白
35 wrapper3-2
36 wrapper2-2
37 wrapper1-2