此篇文章包含装饰器四部分内容:
- 简单装饰器
- 装饰器的传参
- 装饰器兼容是否传参
- 多个装饰器的执行顺序
介绍装饰器之前,先介绍一下闭包。
闭包
有以下这个函数:1
2
3
4
5
6
7
8
9
10def outer(a, b):
print(a, b)
def inner():
print(a + 1, b + 1)
return inner
outer_result = outer(666, 888)
print(outer_result)
print(type(outer_result))
outer_result()
输出结果1
2
3
4666 888
<function outer.<locals>.inner at 0x100fcc158>
<class 'function'>
667 889
闭包实现了一个非常重要的功能:
参数缓存。我可以把参数都先放起来到一边,先去干别的,等我准备好了的时候再调用内部函数就可以了
最简单的装饰器
装饰器正是闭包的一个实现,装饰器也是闭包。只不过,闭包一般都是传变量进去,但装饰器是传函数名(函数定义所在内存的物理地址位置,类似指针)进去。
看下面这个例子:
比如说,我们需要做一个菜,1
2
3
4
5
6
7
8def cooking():
print("洗菜")
print("宽油下锅")
print("下菜落锅")
print("加调味料")
print("耐心翻炒")
print("尝尝味道")
print("装碟")
现在做菜的函数有了,但是还没有食材啊,巧妇难为无米之炊,然后我们不想在做菜的函数里去写与做菜无关的事情,那么装饰器可以实现在不改变做菜函数的基础上先去买菜。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19def buy_online(func):
def wrapper():
print("线上生鲜到家买买买")
func()
return wrapper
def cooking():
print("洗菜")
print("宽油下锅")
print("下菜落锅")
print("加调味料")
print("耐心翻炒")
print("尝尝味道")
print("装碟")
cooking()
输出结果1
2
3
4
5
6
7
8线上生鲜到家买买买
洗菜
宽油下锅
下菜落锅
加调味料
耐心翻炒
尝尝味道
装碟
上面的buy_online就是一个闭包,只不过现在传进去的参数是一个函数而已。
那么传进去给闭包的参数是函数的话,那个这个函数就被叫做装饰器。
使用装饰器很简单,只需要在被装饰的函数的上方加@装饰器函数名就可以了。
@的工作机制
@会把下面被装饰的函数名作为一个参数传递给装饰器,然后把装饰器的返回值赋值给被装饰的函数名,最终被装饰的函数名保存的是装饰器的内部函数的地址。
装饰器的传参
实现不同情况装饰器进行不同的处理,那么需要给装饰器进行传参控制。要实现装饰器参数的缓存,需要再进行一层闭包,即通过两层闭包实现给装饰器传参。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21def buy_online(mall):
def which_mall_online(func):
def wrapper(*args, **kwargs):
print("'{}' 线上生鲜到家 买买买".format(mall))
func(*args, **kwargs)
return wrapper
return which_mall_online
def cooking(*args, **kwargs):
print("洗菜")
print("宽油下锅")
print("下菜落锅")
print("加调味料")
print("耐心翻炒")
print("尝尝味道")
print("装碟")
输出结果1
2
3
4
5
6
7
8'每日优鲜' 线上生鲜到家 买买买
洗菜
宽油下锅
下菜落锅
加调味料
耐心翻炒
尝尝味道
装碟
装饰器兼容传参和不传参
如果希望上面的buy_online装饰器同时支持可以传参也可以不传参或者向第一个例子那样调用,如何实现一个装饰器同时支持下面的调用兼容呢?1
2
3@buy_online
@buy_online('每日优鲜')
@buy_online()
兼容方案思路:
- 有传参和不传参的区别在于,有传参需要再闭包封装一次
- 如果没有参数的话,我们手动调用最外层函数则返回了内部的第二个函数
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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58def buy_online(decorated_func=None, mall=None):
if decorated_func and mall:
raise Exception('被装饰器装饰的函数和装饰器的参数不会在同一处出现')
def which_mall_online(func):
def wrapper(*args, **kwargs):
nonlocal mall
if mall == None:
mall = ''
print("{} 线上生鲜到家 买买买".format(mall))
func(*args, **kwargs)
return wrapper
if decorated_func:
# 如果是传递了被装饰的函数,则直接返回最内层wrapper函数
return which_mall_online(decorated_func)
else:
# 进入这里,说明是装饰器是有参数或使用默认参数的,返回外层函数
return which_mall_online
@buy_online(mall='饿了么生鲜')
def cooking(*args, **kwargs):
print("洗菜")
print("宽油下锅")
print("下菜落锅")
print("加调味料")
print("耐心翻炒")
print("尝尝味道")
print("装碟")
@buy_online()
def cooking_default_parameter(*args, **kwargs):
print("洗菜")
print("宽油下锅")
print("下菜落锅")
print("加调味料")
print("耐心翻炒")
print("尝尝味道")
print("装碟")
@buy_online
def cooking_decorator_no_parameter(*args, **kwargs):
print("洗菜")
print("宽油下锅")
print("下菜落锅")
print("加调味料")
print("耐心翻炒")
print("尝尝味道")
print("装碟")
# 执行正常
cooking()
cooking_default_parameter()
cooking_decorator_no_parameter()
多个装饰器的执行顺序
说明多个装饰器的执行顺序,我们以最后达到了人生巅峰为例子。
在达到人生巅峰之前,需要走过很多坎坷或者不同的阶段。
那么我们以”达到人生巅峰?“为被装饰的函数,在它的上面不断的叠加不同的阶端。1
2
3
4
5
6
7
8
9受精卵
婴儿
幼稚园
小学
初中
高中
大学
社会大学
走向人生巅峰?
各个阶段装饰器
1 | def zero_stage(func): |
走向人生巅峰
1 | def peak_of_life(): |
函数定义时的执行顺序
1 | @zero_stage |
输出结果1
2
3
4
5
6
7进入social_stage函数定义
进入university_stage函数定义
进入 high_school_stage函数定义
进入 middle_school_stage函数定义
进入primary_school_stage函数定义
进入kindergarten_stage函数定义
进入zero_stage函数定义
函数定义时的顺序
最终,
peak_of_life指向的是zero_inner,
zero_inner的func又指向了kindergarten_inner,
kindergarten_inner的func又指向了primary_school_stage_inner,
以此类推,….
函数调用时的执行顺序
1 | peak_of_life() |
输出结果1
2
3
4
5
6
7
8还是个受精卵
懵懵懂懂进入了幼稚园
每天佩戴红领巾的小学生
上晚自习的初中生
一个月只能回一次家的高中生
从此知道了二进制的CS大学生
社会大学,深不可测
走向人生巅峰?
函数调用时的顺序,因为函数定义时的顺序决定了调用时的顺序。
执行peak_of_life()
即为执行zero_inner(),以此类推
总结多个装饰器时的顺序
- 被装饰函数定义时,由被装饰函数由下而上
- 被装饰函数调用时时,从装饰器由上而下的顺序执行
通俗的说:
- 被装饰器装饰的函数定义时就像穿衣服,先穿最里面的
- 被装饰器装饰的函数被调用时就像脱衣服,先脱最外面的
想进一步学习的话,这里有很多电子书