前言

decorator – 装饰器,返回值为另一个函数的函数,通常使用 @wrapper 语法形式来进行函数变换。 装饰器的常见例子包括 classmethod() 和 staticmethod()。

装饰器语法只是一种语法糖,以下两个函数定义在语义上完全等价:

1
2
3
4
5
6
7
def f(arg):
...
f = staticmethod(f)

@staticmethod
def f(arg):
...

源教程地址: https://blog.csdn.net/weixin_44992737/article/details/125868592 .

本文的源代码仓库: https://github.com/LuYF-Lemon-love/susu-python-packages-notes/tree/main/04_decorator .

操作系统:Windows 10 专业版

参考文档

  1. python装饰器详解
  2. decorator – 装饰器
  3. 函数定义

装饰器的官方定义

装饰器本质上是一个Python函数(其实就是闭包),它可以让其他函数在不需要做任何代码变动的前提下增加额外功能,装饰器的返回值也是一个函数对象。装饰器用于有以下场景,比如:插入日志、性能测试、事务处理、缓存、权限校验等场景。

给某个函数加上一个装饰器

一般写法

  1. 新建文件 01-ordinary.py:
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
# 为函数添加一个统计运行时长的功能
import time
import threading

def how_much_time(func):
def inner():
t_start = time.time()
func()
t_end = time.time()
print("一共花费了{0}秒时间".format(t_end - t_start))
return inner

def sleep_5s():
time.sleep(5)
print("%d秒结束了" % (5))

def sleep_6s():
time.sleep(6)
print("%d秒结束了" % (6))

sleep_5s = how_much_time(sleep_5s)
sleep_6s = how_much_time(sleep_6s)

t1 = threading.Thread(target=sleep_5s)
t2 = threading.Thread(target=sleep_6s)
t1.start()
t2.start()
  1. 运行:
1
2
3
4
5
6
$ python 01-ordinary.py
5秒结束了
一共花费了5.002536058425903秒时间
6秒结束了
一共花费了6.000275373458862秒时间
$

装饰器写法

  1. 新建文件 02-decorator.py:
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
# 为函数添加一个统计运行时长的功能
import time
import threading

def how_much_time(func):
def inner():
t_start = time.time()
func()
t_end = time.time()
print("一共花费了{0}秒时间".format(t_end - t_start, ))

return inner

@how_much_time
def sleep_5s():
time.sleep(5)
print("%d秒结束了" % (5))

@how_much_time
def sleep_6s():
time.sleep(6)
print("%d秒结束了" % (6))

t1 = threading.Thread(target=sleep_5s)
t2 = threading.Thread(target=sleep_6s)
t1.start()
t2.start()
  1. 运行:
1
2
3
4
5
6
7
$ python 02-decorator.py
5秒结束了
一共花费了5.001528263092041秒时间
6秒结束了
一共花费了6.000848054885864秒时间

$

给一个函数添加两个装饰器

  1. 新建 03-two-decorators.py:
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
# 为函数添加一个统计运行时长的功能以及日志记录功能
import time
import threading

def how_much_time(func):
print("how_much_time 外层开始")
def inner():
print("how_much_time 内层开始")
t_start = time.time()
func()
t_end = time.time()
print("一共花费了{0}秒时间".format(t_end - t_start))
print("how_much_time 内层结束")
print("how_much_time 外层结束")
return inner

def mylog(func):
print("mylog 外层开始")
def inner():
print("mylog 内层开始")
func()
print("mylog 内层结束")
print("mylog 外层结束")
return inner

@mylog
@how_much_time
def sleep_5s():
print("sleep_5s 开始")
time.sleep(5)
print("%d秒结束了" % (5))
print("sleep_5s 结束")

sleep_5s()
  1. 运行:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$ python 03-two-decorators.py
how_much_time 外层开始
how_much_time 外层结束
mylog 外层开始
mylog 外层结束
mylog 内层开始
how_much_time 内层开始
sleep_5s 开始
5秒结束了
sleep_5s 结束
一共花费了5.002262592315674秒时间
how_much_time 内层结束
mylog 内层结束

$

带参数装饰器

  1. 新建 04-passing-parameters.py:
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
import time

def how_much_time(func):
def inner(*args, **kwargs):
t_start = time.time()
func(*args, **kwargs)
t_end = time.time()
print("一共花费了{0}秒时间".format(t_end - t_start, ))
return inner

@how_much_time
def sleep_ns(n):
time.sleep(n)
print("%d秒结束了" % (5,))

def mylog(type):
def decorator(func):
def inner(*args, **kwargs):
if type == "文件":
print("文件中: 日志记录")
else:
print("控制台: 日志记录")
func(*args, **kwargs)
return inner
return decorator

@mylog("文件")
def func(a, b):
print("func: ", a, b)

sleep_ns(5)
print("*" * 42)
func(100, 200)
  1. 运行:
1
2
3
4
5
6
7
8
$ python 04-passing-parameters.py
5秒结束了
一共花费了5.000115871429443秒时间
******************************************
文件中: 日志记录
func: 100 200

$

wraps语法糖

因为装饰器实质是就是一个函数,是一个被修饰过函数,它与原来未被修饰的函数是两个不同的函数对象。所以,这个装饰器丢失了原来函数对象的一些属性,比如:__name____doc__等属性。使用wraps语法糖可以保留这些属性。

  1. 新建 05-wraps.py:
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
import time
from functools import wraps

def how_much_time(func):
def inner(*args, **kwargs):
t_start = time.time()
func(*args, **kwargs)
t_end = time.time()
print("函数文档:", func.__doc__)
print("一共花费了{0}秒时间".format(t_end - t_start))
return inner

@how_much_time
def sleep_ns_time(n):
"""sleep_ns_time 的文档"""
time.sleep(n)
print("%d秒结束了" % (5,))

def mylog(func):
@wraps(func)
def inner(*args, **kwargs):
func(*args, **kwargs)
print("函数文档(wraps):", func.__doc__)
print("日志记录(wraps)...")
return inner

@mylog
def sleep_ns_log(n):
"""sleep_ns_log 的文档(wraps)"""
time.sleep(n)
print("%d秒结束了" % (5,))

sleep_ns_time(5)
print("函数文档--->", sleep_ns_time.__doc__)
print("*" * 42)
sleep_ns_log(5)
print("函数文档(wraps)--->", sleep_ns_log.__doc__)
  1. 运行:
1
2
3
4
5
6
7
8
9
10
11
12
$ python 05-wraps.py
5秒结束了
函数文档: sleep_ns_time 的文档
一共花费了5.000011920928955秒时间
函数文档---> None
******************************************
5秒结束了
函数文档(wraps): sleep_ns_log 的文档(wraps)
日志记录(wraps)...
函数文档(wraps)---> sleep_ns_log 的文档(wraps)

$

类装饰器

类装饰器这个写法,主要思路就是返回一个增加了新功能的函数对象,只不过这个函数对象是一个类的实例对象。由于装饰器是可调用对象,所以必须在类里面实现__call__方法,这样由类生成的各种实例加上()就可以运行了。

不带参数的类装饰器

  1. 新建 06-class-decorator.py:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import time

class Decorator:
def __init__(self, func):
self.func = func

def defer_time(self):
time.sleep(5)
print("延时结束了")

def __call__(self, *args, **kwargs):
self.defer_time()
self.func()

@Decorator
def f1():
print("延时之后我才开始执行")

f1()
  1. 运行:
1
2
3
4
5
$ python 06-class-decorator.py
延时结束了
延时之后我才开始执行

$

带参数的类装饰器

  1. 新建 07-class-passing-parameters.py:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import time

class Decorator:
def __init__(self, func):
self.func = func

def defer_time(self,time_sec):
time.sleep(time_sec)
print(f"{time_sec}s延时结束了")

def __call__(self, time):
self.defer_time(time)
self.func()

@Decorator
def f1():
print("延时之后我才开始执行")

f1(5)
  1. 运行:
1
2
3
4
5
$ python 07-class-passing-parameters.py
5s延时结束了
延时之后我才开始执行

$

结语

第八十三篇博文写完,开心!!!!

今天,也是充满希望的一天。