简单的装饰器使用
比如现在公司有一个需求,每个函数都要计算运行时间,我们可以调用time模块实现一个简单的计算执行时间的方法
1 | import time |
那如果要是有200多个函数呢,难道要一个个加入,然后在一个个删除?我们想到计算时间可以单独写一个函数去调用。1
2
3
4
5
6
7
8
9
10
11
12# 调用统计时间函数
def timmer(f):
start_time = time.time()
f()
end_time = time.time()
print(end_time - start_time)
def func():
time.sleep(3)
print('func 1')
timmer(func)
这样以后的200个函数都要使用timmer去调用执行么?也是不合理的,应该是func方法来调用时间函数,比较合理。
装饰器的形成过程
1 | # 我们想要做到的: |
运行过程流程图:
- 原来的函数为func
- 最后我还是要调用func
- 中间增加的计时功能timmer
- 通过func = timmer(func) 和 闭包函数 来进行修饰
- 最终通过闭包函数来返回内部函数 交给 外部的func接收,接收的变量还是原本func的方法
- 最后执行外部的func(),他会自动去找装饰函数inner(),再去找到原本被装饰的函数func()
总结:
装饰器的本质:一个闭包函数
装饰器的功能:在不修改原函数及其调用方式的情况下对原函数功能进行扩展
装饰器的意义: 装饰器既没有改变函数的调用方式,又在函数的前后增加了装饰功能
开放封闭原则
开放: 对扩展是开放的,任何一个程序,不可能在设计之初就已经想好了所有的功能并且未来不做任何更新和修改。所以我们必须允许代码扩展、添加新功能。
封闭: 对修改是封闭的,因为我们写的一个函数,很有可能已经交付给其他人使用了,如果这个时候我们对其进行了修改,很有可能影响其他已经在使用该函数的用户。
装饰器完美的遵循了这个开放封闭原则
语法糖
@装饰器函数 == 重新定义被装饰函数=装饰器函数(被装饰函数)1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19import time
def timmer(f): # 装饰器函数
def inner():
start_time = time.time()
f() # 被装饰的函数
end_time = time.time()
print(end_time - start_time)
return inner
# 语法糖 @timmer 让代码更好看 更便捷
# 在被装饰的函数上面贴着加上 @装饰器函数名
# 就相当于写了func = timmer(func)
def func():
time.sleep(3)
print('func 1')
# func = timmer(func)
func()
装饰带返回值的函数的装饰器
1 | import time |
装饰带一个参数的函数
1 |
|
接收万能参数装饰器
1 | def wrapper(func): |
装饰器的固定格式
1 | import time |
1 | def wrapper(func): # func = qqxing |
装饰器的固定格式 - wraps
首先先了解函数的name和doc方法:
函数名.__name__
= 查看字符串格式的函数名
函数名.__doc__
= 查看函数注释1
2
3
4
5
6
7
8
9def wahaha():
'''
一个打印娃哈哈的函数
:return:
'''
print('娃哈哈')
print(wahaha.__name__) # 查看字符串格式的函数名
print(wahaha.__doc__) # 查看函数注释
在执行使用装饰器之后,我们打印函数的name发现是装饰器的函数名称了,这个时候就需要使用wraps来解决。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25from functools import wraps
def wrapper(func): # func = holiday
def inner(*args,**kwargs):
print('在被装饰的函数执行前做的事')
ret = func(*args,**kwargs)
print('在被装饰的函数执行后做的事')
return ret
return inner
def holiday(day):
'''
这是一个放假通知
:param day:
:return:
'''
return '还有%s天放假'%day
print(holiday.__name__) # inner...因为现在的holiday已经是inner了,由于之前说装饰器最好不要影响被装饰的函数,需要用wraps装饰inner函数,才可以正常显示回去
print(holiday.__doc__)
ret = holiday(3) # inner
print(ret)
# wraps并不影响wrapper装饰器的使用
带参数的装饰器
比如现在有500个函数,都使用装饰器,那么怎么一次性的去控制500个装饰器的增加和删除,怎么办?我们可以使用带参数的参数器,通过标志位参数去控制装饰器是否执行。
带参数的装饰器,也就是三层装饰器,在外部多一次调用传入状态标记。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
30import time
FLAGE = True # 标识位,True执行,Fales不执行
def timmer_out(flag): # 在原有装饰器之外再来一层
def timmer(func):
def inner(*args,**kwargs):
if flag: # 如果flag = True 那么我就走装饰器,否则我就只运行被装饰的函数
start_time = time.time()
ret = func(*args,**kwargs)
end_time = time.time()
print(end_time - start_time)
return ret
else:
ret = func(*args, **kwargs)
return ret
return inner
return timmer
# timmer = timmer_out(FLAGE)
def wahaha():
time.sleep(2)
print('wahaha')
def qqxing():
time.sleep(1)
print('qqxing')
ret = wahaha()
ret = qqxing()
多个装饰器装饰一个函数
多个装饰器执行的过程有点像套娃,装饰器在后的先执行装饰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
32def wrapper1(func): # f
def inner1():
print('wrapper1装饰器 start') # 3
func() # 执行f # 4
print('wrapper1装饰器 end') # 5
return inner1
def wrapper2(func): # inner1
def inner2():
print('wrapper2装饰器 start') # 1 先执行他
func() # inner1() # 2
print('wrapper2装饰器 end') # 6
return inner2
# 先看装饰器执行先后
def f():
print('in f')
f() # ==> 调用开始现在是 inner2
# wrapper2装饰器 start
# wrapper1装饰器 start
# in f
# wrapper1装饰器 end
# wrapper2装饰器 end
# 1. 先看装饰器执行先后,wrapper2没有找到要被修饰的函数,所以现在wrapper1
# 2. f = wrapper1(f) = inner1
# 3. # f(下面赢变成inner1) ==> inner1 = wrapper2(inner1) = inner2,但是传进去的是inner1,
有时候会遇见两个需求:
- 记录用户的登录情况
- 记录函数的执行时间
仔细思考下先后执行顺序:先登录成功之后 才能开始执行程序记录函数的执行时间