装饰器

1. 为什么要使用装饰器

开放封闭原则:软件一旦上线后,就应该遵循开放封闭原则,即对修改源代码是封闭的,对功能的扩展是开放的。

也就是说我们必须找到一种解决方案:能够在不修改一个功能源代码以及调用方式的情况下,为其加上新功能。

总结:原则如下:

  1. 不修改源代码
  2. 不修改调用方式

目的:

在遵循1、2的基础上扩展新功能

2. 什么是装饰器

器→ 指代工具,装饰指的是为被装饰对象添加新功能

完整含义:

  1. 装饰器即在不修改被装饰对象源代码和调用方式的前提下,为被装饰对象添加新功能
  2. 装饰器与被装饰器对象均可是任意可调用对象
  3. 装饰器→ 函数
  4. 被装饰对象→函数

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