装饰器
1. 为什么要使用装饰器
开放封闭原则:软件一旦上线后,就应该遵循开放封闭原则,即对修改源代码是封闭的,对功能的扩展是开放的。
也就是说我们必须找到一种解决方案:能够在不修改一个功能源代码以及调用方式的情况下,为其加上新功能。
总结:原则如下:
- 不修改源代码
- 不修改调用方式
目的:
在遵循1、2的基础上扩展新功能
2. 什么是装饰器
器→ 指代工具,装饰指的是为被装饰对象添加新功能
完整含义:
- 装饰器即在不修改被装饰对象源代码和调用方式的前提下,为被装饰对象添加新功能
- 装饰器与被装饰器对象均可是任意可调用对象
- 装饰器→ 函数
- 被装饰对象→函数
3. 无参装饰器
1
2
3
4
5
|
def outer(func):
def inner(*args, **kwargs):
res = func(*args, **kwargs)
return res
return inner
|
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
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
|
1 ###现在我们要查看指令执行的时间
2 #以前实现的方式
3 #违反了装饰器的原则---》不修改源代码
4 import time #导入时间模块
5
6 def index():
7 start_time=time.time() #现在时间
8 time.sleep(3) #暂停3s
9 print('welcome to index page')
10 stop_time=time.time() #现在的时间(time.sleep与print执行完后的时间)
11 print('run time is %s' %(stop_time-start_time))
12
13 index() #调用函数
14
15 #修订1
16 #没有违背原则,但是我又要给一个程序加一个同样的这样的功能呢?会体现出现在的代码重复
17 import time
18
19 def index():
20 time.sleep(3)
21 print('welcome to index page')
22
23 start_time=time.time()
24 index()
25 stop_time = time.time()
26 print('run time is %s' % (stop_time - start_time))
27
28 #但是我又要给一个程序加一个同样的这样的功能呢?会体现出现在的代码重复
29
30 import time
31
32 def index():
33 time.sleep(3)
34 print('welcome to index page')
35
36 def home(name):
37 time.sleep(5)
38 print('welcome %s to home page' %name)
39
40 start_time=time.time() #代码重复
41 index()
42 stop_time = time.time() #代码重复
43 print('run time is %s' % (stop_time - start_time)) #代码重复
44
45 start_time=time.time()
46 home('egon')
47 stop_time = time.time()
48 print('run time is %s' % (stop_time - start_time))
49
50 #修订2
51 # 违反了原则2
52 # 修改了原函数的调用方式
53
54 import time
55
56 def index():
57 time.sleep(3)
58 print('welcome to index page')
59
60 def home(name):
61 time.sleep(5)
62 print('welcome %s to home page' %name)
63
64 def wrapper(func): #func=index
65 start_time=time.time()
66 func() #index()
67 stop_time = time.time()
68 print('run time is %s' % (stop_time - start_time))
69
70
71 wrapper(index) # 修改了原函数的调用方式
72
73 #修订3
74
75 import time
76
77 def index():
78 time.sleep(3)
79 print('welcome to index page')
80
81 def outter(func): #func=最原始的index
82 # func=最原始的index
83 def wrapper():
84 start_time=time.time()
85 func()
86 stop_time=time.time()
87 print(stop_time-start_time)
88 return wrapper
89
90
91 index=outter(index) # 新的index=wrapper
92
93 index() #wrapper()
无参装饰器
|
含参装饰器升级版
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
|
import time
def index():
time.sleep(1)
print('welcome to index page')
return 122
def home(name):
time.sleep(2)
print('welcome %s to home page' %name)
#==============装饰器
def timmer(func):
#func=最原始的home
def wrapper(*args,**kwargs):
start_time=time.time()
res=func(*args,**kwargs) #调用最原始的home
stop_time=time.time()
print(stop_time-start_time)
return res
return wrapper
index=timmer(index) # 新的index=wrapper
home=timmer(home) #新的home=wrapper
# ==========================================
# res=index() #res=wrapper()
# print(res)
home(name='egon') #wrapper('egon')
index() #wrapper()
|
Wraps
Wrap 用来做什么
像上面的装饰器,当我们调用func.name,func.doc(被包装的函数/原函数) 时,返回的都是wrapper的名称和文档。
我们希望装饰器可以不影响被包装函数的名称和使用。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
def a_decorator(func):
def wrapper(*args, **kwargs):
"""A wrapper function"""
# Extend some capabilities of func
func()
return wrapper
@a_decorator
def first_function():
"""This is docstring for first function"""
print("first function")
@a_decorator
def second_function(a):
"""This is docstring for second function"""
print("second function")
print(first_function.__name__)
print(first_function.__doc__)
print(second_function.__name__)
print(second_function.__doc__)
|
像下面这样,我们解决了部分问题。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
def a_decorator(func):
def wrapper(*args, **kwargs):
"""A wrapper function"""
# Extend some capabilities of func
func()
wrapper.__name__ = func.__name__
wrapper.__doc__ = func.__doc__
return wrapper
@a_decorator
def first_function():
"""This is docstring for first function"""
print("first function")
@a_decorator
def second_function(a):
"""This is docstring for second function"""
print("second function")
print(first_function.__name__)
print(first_function.__doc__)
print(second_function.__name__)
print(second_function.__doc__)
|
但是当我们调用help(first_function)时,我们发现函数的签名却是
1
2
3
4
|
Help on function first_function in module __main__:
**first_function(*args, **kwargs)**
This is docstring for first function
|
函数的签名发生了变化。
但是只要我们在最内层的装饰函数上添加语句@wraps(func)
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 a_decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
"""A wrapper function"""
# Extend some capabilities of func
func()
return wrapper
@a_decorator
def first_function():
"""This is docstring for first function"""
print("first function")
@a_decorator
def second_function(a):
"""This is docstring for second function"""
print("second function")
print(first_function.__name__)
print(first_function.__doc__)
print(second_function.__name__)
print(second_function.__doc__)
|
像这样,什么问题都解决了。
来道题目
七:为题目五编写装饰器,实现缓存网页内容的功能:具体:实现下载的页面存放于文件中,如果文件内有值(文件大小不为0),就优先从文件中读取网页内容,否则,就去下载,然后存到文件中
扩展功能:用户可以选择缓存介质/缓存引擎,针对不同的url,缓存到不同的文件中
九 编写日志装饰器,实现功能如:一旦函数f1执行,则将消息2017-07-21 11:12:11 f1 run写入到日志文件中,日志文件路径可以指定
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
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
|
#题目七:简单版本
import requests
import os
cache_file='cache.txt'
def make_cache(func):
def wrapper(*args,**kwargs):
if not os.path.exists(cache_file):
with open(cache_file,'w'):pass
if os.path.getsize(cache_file):
with open(cache_file,'r',encoding='utf-8') as f:
res=f.read()
else:
res=func(*args,**kwargs)
with open(cache_file,'w',encoding='utf-8') as f:
f.write(res)
return res
return wrapper
@make_cache
def get(url):
return requests.get(url).text
# res=get('https://www.python.org')
# print(res)
#题目七:扩展版本
import requests,os,hashlib
engine_settings={
'file':{'dirname':'./db'},
'mysql':{
'host':'127.0.0.1',
'port':3306,
'user':'root',
'password':'123'},
'redis':{
'host':'127.0.0.1',
'port':6379,
'user':'root',
'password':'123'},
}
def make_cache(engine='file'):
if engine not in engine_settings:
raise TypeError('egine not valid')
def deco(func):
def wrapper(url):
if engine == 'file':
m=hashlib.md5(url.encode('utf-8'))
cache_filename=m.hexdigest()
cache_filepath=r'%s/%s' %(engine_settings['file']['dirname'],cache_filename)
if os.path.exists(cache_filepath) and os.path.getsize(cache_filepath):
return open(cache_filepath,encoding='utf-8').read()
res=func(url)
with open(cache_filepath,'w',encoding='utf-8') as f:
f.write(res)
return res
elif engine == 'mysql':
pass
elif engine == 'redis':
pass
else:
pass
return wrapper
return deco
@make_cache(engine='file')
def get(url):
return requests.get(url).text
# print(get('https://www.python.org'))
print(get('https://www.baidu.com'))
|
题目八
八:还记得我们用函数对象的概念,制作一个函数字典的操作吗,来来来,我们有更高大上的做法,在文件开头声明一个空字典,然后在每个函数前加上装饰器,完成自动添加到字典的操作
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
|
route_dic={}
def make_route(name):
def deco(func):
route_dic[name]=func
return deco
@make_route('select')
def func1():
print('select')
@make_route('insert')
def func2():
print('insert')
@make_route('update')
def func3():
print('update')
@make_route('delete')
def func4():
print('delete')
print(route_dic)
print(route_dic['select'])
print(route_dic['select']())
# 输出
>>>{'select': <function func1 at 0x0166E6A8>, 'insert': <function func2 at 0x017565D0>, 'update': <function func3 at 0x017562B8>, 'delete': <function func4 at 0x01756618>}
>>><function func1 at 0x0164E6A8>
>>>select
None
|