python装饰器
开闭原则:
在不修改原函数及其调用方式的情况下对原函数功能进行扩展
对代码的修改是封闭
不能修改被装饰的函数的源代码
不能修改被装饰的函数的调用方式
用函数的方式设想一下游戏里用枪的场景
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