Python三大利器 — 装饰器


简单的装饰器使用

比如现在公司有一个需求,每个函数都要计算运行时间,我们可以调用time模块实现一个简单的计算执行时间的方法

1
2
3
4
5
6
7
8
9
10
11
import time
# 统计每个函数的执行时间 1
def func():
start_time = time.time()
print('func 1')
time.sleep(3)
now_time = time.time()
return now_time - start_time

ret = func() # func 1
print(ret) # 3.000171661376953

那如果要是有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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 我们想要做到的:
# 1 不想修改函数的调用方式 但是还想再原来的函数前后添加功能
# 2 timmer就是一个装饰器函数,只是对一个函数 有一些装饰作用

def func():
time.sleep(3)
print('func 1')

# 调用统计时间函数
# 闭包 内部函数inner,调用了外部变量f,f是传进来的
def timmer(f): # 装饰器函数
def inner():
start_time = time.time()
f() # 被装饰的函数
end_time = time.time()
print(end_time - start_time)
return inner

func = timmer(func)
func()

运行过程流程图:

  1. 原来的函数为func
  2. 最后我还是要调用func
  3. 中间增加的计时功能timmer
  4. 通过func = timmer(func) 和 闭包函数 来进行修饰
  5. 最终通过闭包函数来返回内部函数 交给 外部的func接收,接收的变量还是原本func的方法
  6. 最后执行外部的func(),他会自动去找装饰函数inner(),再去找到原本被装饰的函数func()

总结:
装饰器的本质:一个闭包函数
装饰器的功能:在不修改原函数及其调用方式的情况下对原函数功能进行扩展
装饰器的意义: 装饰器既没有改变函数的调用方式,又在函数的前后增加了装饰功能

开放封闭原则

开放: 对扩展是开放的,任何一个程序,不可能在设计之初就已经想好了所有的功能并且未来不做任何更新和修改。所以我们必须允许代码扩展、添加新功能。

封闭: 对修改是封闭的,因为我们写的一个函数,很有可能已经交付给其他人使用了,如果这个时候我们对其进行了修改,很有可能影响其他已经在使用该函数的用户。

装饰器完美的遵循了这个开放封闭原则

语法糖

@装饰器函数 == 重新定义被装饰函数=装饰器函数(被装饰函数)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import 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)
@timmer
def func():
time.sleep(3)
print('func 1')
# func = timmer(func)
func()

装饰带返回值的函数的装饰器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import time

def timmer(f): # 装饰器函数
def inner():
start_time = time.time()
ret = f() # 被装饰的函数 带有返回值
end_time = time.time()
print(end_time - start_time)
return ret # 返回被装饰的函数的返回值
return inner
@timmer
def func():
time.sleep(3)
print('func 1')
return '新年好' # 被装饰的函数的返回值

# func = timmer(func)
ret = func()
print(ret)

# 因为现在的func不是原来的func 而是inner ,所有要对inner中增加返回值
# 现在的func就是inner,ret接收的事inner的返回值

装饰带一个参数的函数

1
2
3
4
5
6
7
8
9
10
11
12
13

def wrapper(func):
def inner(name):
ret = func(name)
return ret
return inner

@wrapper # func = wrapper(func)
def func(name):
return '新年好,%s'%name

ret = func('leo')
print(ret)

接收万能参数装饰器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
def wrapper(func):
def inner(*args,**kwargs):
# print('函数被装饰之前要做的事')
print(*args) # leo python
print(kwargs) # {'age': 30}
print(kwargs['age']) # 30
ret = func(*args,**kwargs)
# print('函数被装饰之前要做的事')
return ret
return inner

@wrapper
def func(name,course,age):
return '大家好,我是%s,今年%d,现在正在学习%s'%(name,age,course)

ret = func('leo','python',age=30)
print(ret) # 大家好,我是leo,今年30,现在正在学习python

装饰器的固定格式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import time
# 单纯就叫装饰器的时候 -- wrapper
def wrapper(f): # 装饰器函数,f是被装饰的函数,装饰器函数里面的参数永远是被装饰的函数
def inner(*args,**kwargs): # 内部函数inner,*args,**kwargs动态参数原封不动的传给被装饰的函数
# 被装饰函数执行之前要做的事
ret = f(*args,**kwargs) # 被装饰的函数,执行完成后,给外面返回值
# 被装饰函数执行之后要做的事
return ret
return inner # 对应内部函数inner 不加括号执行

@wrapper # func = timmer(func)
def func(a,b):
time.sleep(2)
print('func1',a,b)
return '新年好'

ret = func(1,2)
print(ret)
1
2
3
4
5
6
7
8
9
10
11
def wrapper(func):  # func = qqxing
def inner(*args,**kwargs):
ret = func(*args,**kwargs) # 被装饰的函数 qqxing
return ret
return inner

@wrapper # qqxing = wrapper(qqxing)
def qqxing(a,b):
print(123)

ret = qqxing(1,2) # 实际上执行的是inner()

装饰器的固定格式 - wraps

首先先了解函数的namedoc方法:
函数名.__name__ = 查看字符串格式的函数名
函数名.__doc__ = 查看函数注释

1
2
3
4
5
6
7
8
9
def 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
25
from functools import wraps

def wrapper(func): # func = holiday
@wraps(func) # 装饰inner函数
def inner(*args,**kwargs):
print('在被装饰的函数执行前做的事')
ret = func(*args,**kwargs)
print('在被装饰的函数执行后做的事')
return ret
return inner

@wrapper # holiday = wrapper(holiday)
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
30
import 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)
@timmer_out(FLAGE)
def wahaha():
time.sleep(2)
print('wahaha')

@timmer_out(FLAGE)
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
32
def 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

# 先看装饰器执行先后
@wrapper2 # f = wrapper2(f)==> f = inner1 ==> inner1 = wrapper2(inner1) ==> inner2 ,传进去的是inner1,最后返回得到的是inner2
@wrapper1 # f = wrapper1(f) = inner1
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,


有时候会遇见两个需求:

  1. 记录用户的登录情况
  2. 记录函数的执行时间
    仔细思考下先后执行顺序:先登录成功之后 才能开始执行程序记录函数的执行时间