Skip to content

Commit d36fc34

Browse files
committed
装饰器
装饰器
1 parent b9f71ac commit d36fc34

1 file changed

Lines changed: 201 additions & 74 deletions

File tree

python_advance/装饰器.md

Lines changed: 201 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -1,90 +1,217 @@
1-
**装饰器:本质上是一个Python函数(嵌套函数),它的参数是另一个函数(被装饰函数),他可以让其他函数在不做任何代码变动的情况下增加额外的功能,装饰器的返回值也是一个函数对象。有了装饰器,可以抽离出大量与函数功能本身无关的代码并重复使用。需求场景如:插入日志、性能测试、权限校验等。**
2-
3-
#### 1.最简单的装饰器
4-
被装饰函数没有参数:
5-
6-
```python
7-
import time
8-
def count_time(func):
9-
def deco():
10-
start = time.time()
11-
func()
12-
end = time.time()
13-
print('{} 耗时: {}'.format(func.__name__, end-start))
14-
return deco
15-
@count_time
16-
def func_a():
17-
time.sleep(3)
18-
19-
if __name__ == '__main__':
20-
# func_a 耗时: 3.0008597373962402
21-
func_a()
22-
```
23-
#### 2.被装饰函数带参数
24-
可变参数+关键字参数可以表示所有的参数情况。
25-
26-
```python
27-
import time
28-
def count_time(func):
29-
def deco(*args, **kwargs):
30-
start = time.time()
31-
func(*args, **kwargs)
32-
end = time.time()
33-
print('{} 耗时: {}'.format(func.__name__, end-start))
34-
return deco
35-
@count_time
36-
def func_a(t):
37-
time.sleep(t)
38-
39-
if __name__ == '__main__':
40-
# func_a 耗时: 1.0008749961853027
41-
func_a(1)
42-
# func_a 耗时: 2.0003809928894043
43-
func_a(2)
44-
```
45-
#### 3.带参数的装饰器
46-
实现一个装饰器,控制被装饰函数的执行次数。三层嵌套。
47-
48-
```python
49-
def repate(num=3):
50-
def actual_deco(func):
51-
def deco(*args, **kwargs):
52-
for i in range(num):
53-
print('{}执行第{}'.format(func.__name__, i+1))
54-
func()
55-
return deco
56-
return actual_deco
57-
@repate(num=3)
58-
def foo():
59-
pass
60-
61-
if __name__ == '__main__':
62-
# foo执行第1次
63-
# foo执行第2次
64-
# foo执行第3次
65-
foo()
66-
```
67-
68-
#### 4.装饰器有一个关键的特性是,它们在被装饰的函数定义之后立即运行。这通常是在导入时(即 Python 加载模块时)
1+
# 装饰器
2+
## 1.装饰器定义
3+
**装饰器的本质是一个闭包(嵌套函数中内函数引用了外函数的局部变量,外函数的返回包含对内函数的引用,这样就形成了一个闭包)。[变量的作用域与闭包](变量的作用域.md)**
4+
5+
* 所以装饰器也可以理解为一个嵌套函数,它的参数是被装饰的函数,返回值也是一个函数对象
6+
* 装饰器可以在原函数代码没有任何变动的基础上,增加额外的功能
7+
* 装饰器的应用场景有:性能测试(计算函数运行时间),插入日志,权限校验(Django的@login_required),缓存(缓存函数的计算结果,避免重复计算,提高效率)
8+
9+
## 2.装饰器的实现
10+
#### 2.1. 最简单的装饰器
11+
计算函数运行时间
6912
```python
70-
registry = []
13+
import time
14+
15+
def count_time(func):
16+
def wrap():
17+
start = time.time()
18+
func()
19+
end = time.time()
20+
print('函数名:{} 耗时:{}'.format(func.__name__, end - start))
21+
return wrap
22+
23+
@count_time
24+
def func():
25+
print('func正在运行...')
26+
time.sleep(3)
27+
print('func运行结束')
28+
29+
if __name__ == '__main__':
30+
func()
31+
```
32+
```python
33+
func正在运行...
34+
func运行结束
35+
函数名:func 耗时:3.0006725788116455
36+
```
37+
38+
#### 2.2. 被装饰的函数带参数
39+
可变参数+关键字参数可以表示所有的参数情况
40+
```python
41+
import time
42+
43+
def count_time(func):
44+
def wrap(*args, **kwargs):
45+
start = time.time()
46+
func(*args, **kwargs)
47+
end = time.time()
48+
print('函数名:{} 耗时:{}'.format(func.__name__, end - start))
49+
return wrap
50+
51+
@count_time
52+
def func(seconds):
53+
print('func正在运行...')
54+
time.sleep(seconds)
55+
print('func运行结束')
56+
57+
if __name__ == '__main__':
58+
func(3)
59+
func(5)
60+
```
61+
62+
#### 2.3. 装饰器带参数
63+
定义一个装饰器,控制被装饰函数运行次数
64+
```python
65+
def run(times):
66+
def deco(func):
67+
def wrap(*args, **kwargs):
68+
for _ in range(times):
69+
func(*args, **kwargs)
70+
return wrap
71+
return deco
72+
73+
@run(times=3)
74+
def func():
75+
print('func正在运行...')
76+
print('func运行结束')
77+
78+
if __name__ == '__main__':
79+
func()
80+
```
81+
```python
82+
func正在运行...
83+
func运行结束
84+
func正在运行...
85+
func运行结束
86+
func正在运行...
87+
func运行结束
88+
```
89+
90+
#### 2.4. 装饰器用类实现(无参数)
91+
CountTime()相当于调用CountTime类的\_\_call__()方法
92+
93+
```python
94+
import time
95+
96+
class CountTime:
97+
def __call__(self, func):
98+
def wrap(*args, **kwargs):
99+
start = time.time()
100+
func(*args, **kwargs)
101+
end = time.time()
102+
print('函数名:{} 耗时:{}'.format(func.__name__, end - start))
103+
return wrap
104+
105+
@CountTime()
106+
def func():
107+
print('func正在运行...')
108+
time.sleep(3)
109+
print('func运行结束')
110+
111+
if __name__ == '__main__':
112+
func()
113+
```
114+
```python
115+
func正在运行...
116+
func运行结束
117+
函数名:func 耗时:3.0221292972564697
118+
```
119+
120+
#### 2.5. 装饰器用类实现(有参数)
121+
通过init初始化方法传参
122+
```python
123+
class Run:
124+
def __init__(self, times):
125+
self.times = times
126+
127+
def __call__(self, func):
128+
def wrap(*args, **kwargs):
129+
for _ in range(self.times):
130+
func(*args, **kwargs)
131+
return wrap
132+
133+
@Run(times=3)
134+
def func():
135+
print('func正在运行...')
136+
print('func运行结束')
137+
138+
if __name__ == '__main__':
139+
func()
140+
```
141+
```python
142+
func正在运行...
143+
func运行结束
144+
func正在运行...
145+
func运行结束
146+
func正在运行...
147+
func运行结束
148+
```
149+
150+
## 3.functools.wraps
151+
以上装饰器存在一个小问题,被装饰函数虽然并没有改动,但是被装饰函数的元信息不见了,如下:
152+
* 被装饰函数的函数名是func,但是经过装饰器装饰后,函数名变为了wrap
153+
154+
```python
155+
import time
156+
157+
def count_time(func):
158+
def wrap():
159+
start = time.time()
160+
func()
161+
end = time.time()
162+
print('函数名:{} 耗时:{}'.format(func.__name__, end - start))
163+
return wrap
164+
165+
@count_time
166+
def func():
167+
print('func正在运行...')
168+
time.sleep(3)
169+
print('func运行结束')
71170

171+
if __name__ == '__main__':
172+
print(func.__name__) # wrap
173+
```
174+
175+
**通过functools.wraps来避免这种问题**
176+
177+
```python
178+
import functools
179+
import time
180+
181+
def count_time(func):
182+
@functools.wraps(func)
183+
def wrap():
184+
start = time.time()
185+
func()
186+
end = time.time()
187+
print('函数名:{} 耗时:{}'.format(func.__name__, end - start))
188+
return wrap
189+
190+
@count_time
191+
def func():
192+
print('func正在运行...')
193+
time.sleep(3)
194+
print('func运行结束')
195+
196+
if __name__ == '__main__':
197+
print(func.__name__) # func
198+
```
199+
## 4.装饰器有一个关键的特性是,它们在被装饰的函数定义之后立即运行。这通常是在导入时(即 Python 加载模块时)
200+
```python
201+
registry = []
72202

73203
def register(func):
74204
print('running register({})'.format(func))
75205
registry.append(func)
76206
return func
77207

78-
79208
@register
80209
def f1():
81210
print('running f1()')
82211

83-
84212
def f2():
85213
print('running f2()')
86214

87-
88215
if __name__ == '__main__':
89216
print(registry)
90217
print('running main')
@@ -101,7 +228,7 @@ running f1()
101228
running f2()
102229
```
103230

104-
#### 5.标准库中的装饰器 functools.lru_cache()
231+
## 5.标准库中的装饰器 functools.lru_cache()
105232
functools.lru_cache是非常实用的装饰器,它实现了备忘功能。这是一项优化技术,它把耗时的函数的结果保存
106233
起来,避免传入相同的参数时重复计算。
107234
* 参数maxsize:指定缓存的最大结果数量
@@ -198,7 +325,7 @@ if __name__ == '__main__':
198325
执行函数fibonacci(5)耗时: 0.0
199326
```
200327

201-
#### 6.标准库中的装饰器 functools.singledispatch
328+
## 6.标准库中的装饰器 functools.singledispatch
202329

203330
Python3.4新增的 functools.singledispatch 装饰器可以把整体方案拆分成多个模块,甚至可以为你无法修改的类提供专门的函数。使用@singledispatch 装饰的普通函数会变成泛函数(generic function):根据第一个参数的类型,以不同方式执行相同操作的一组函数.
204331

0 commit comments

Comments
 (0)