Python中常见的装饰器(decorator)

本文将会介绍Python中几个工作中常见的装饰器。

本文将会介绍Python中几个日常工作中常用的装饰器(decorator),如下:

  • 计时装饰器
  • debug装饰器
  • 日志装饰器
  • 缓存装饰器
  • retry装饰器
  • 其它装饰器

计时装饰器

计时装饰器用于统计函数的运行时间,采用time模块,记录函数运行前、后时间,得到函数运行时间。示例Python代码如下:

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
# -*- coding: utf-8 -*-
import time
from functools import wraps


def timer(func):
@wraps(func)
def wrapper(*args, **kwargs):
start = time.time()
result = func(*args, **kwargs)
end = time.time()
print(f"Execution time of {func.__name__}: {end - start} seconds")
return result
return wrapper


@timer
def time_sleep(n):
# sleep for n seconds
time.sleep(n)
return n


if __name__ == '__main__':
t = time_sleep(3)
print(t)

执行结果:

1
2
Execution time of time_sleep: 3.0027120113372803 seconds
3

debug装饰器

debug装饰器用于代码debug,打印出运行函数的函数名及参数,用于debug。示例Python代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# -*- coding: utf-8 -*-
from functools import wraps


def debug(func):
@wraps(func)
def wrapper(*args, **kwargs):
print(f"Calling {func.__name__} with args: {args} and kwargs: {kwargs}")
result = func(*args, **kwargs)
print(f"{func.__name__} returned: {result}")
return result
return wrapper


@debug
def greet(name, message="Hello"):
"""Returns a greeting message with the name"""
return f"{message}, {name}!"


if __name__ == '__main__':
print(greet("Alice"))
print(greet("Bob", message="Hi"))

执行结果:

1
2
3
4
5
6
Calling greet with args: ('Alice',) and kwargs: {}
greet returned: Hello, Alice!
Hello, Alice!
Calling greet with args: ('Bob',) and kwargs: {'message': 'Hi'}
greet returned: Hi, Bob!
Hi, Bob!

日志装饰器

日志装饰器用于在函数运行时打印日志,常用的日志模块有logging, loguru等。示例Python代码如下:

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
# -*- coding: utf-8 -*-
from functools import wraps
import logging

logging.basicConfig(level=logging.DEBUG)
logger = logging.getLogger()


def log(func):
@wraps(func)
def wrapper(*args, **kwargs):
args_repr = [repr(a) for a in args]
kwargs_repr = [f"{k}={v!r}" for k, v in kwargs.items()]
signature = ", ".join(args_repr + kwargs_repr)
logger.debug(f"function {func.__name__} called with args {signature}")
try:
result = func(*args, **kwargs)
return result
except Exception as e:
logger.exception(f"Exception raised in {func.__name__}. exception: {str(e)}")
raise e
return wrapper


@log
def divide(a, b, message='hi'):
return a/b, message


if __name__ == '__main__':
print(divide(2, 1, message='good'))
print(divide(1, 0, message='bad'))

执行结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
DEBUG:root:function divide called with args 2, 1, message='good'
DEBUG:root:function divide called with args 1, 0, message='bad'
ERROR:root:Exception raised in divide. exception: division by zero
Traceback (most recent call last):
File "/Users/admin/PycharmProjects/env_test/custom_decorator/log_decorator.py", line 17, in wrapper
result = func(*args, **kwargs)
File "/Users/admin/PycharmProjects/env_test/custom_decorator/log_decorator.py", line 27, in divide
return a/b, message
ZeroDivisionError: division by zero
Traceback (most recent call last):
File "/Users/admin/PycharmProjects/env_test/custom_decorator/log_decorator.py", line 32, in <module>
print(divide(1, 0, message='bad'))
File "/Users/admin/PycharmProjects/env_test/custom_decorator/log_decorator.py", line 21, in wrapper
raise e
File "/Users/admin/PycharmProjects/env_test/custom_decorator/log_decorator.py", line 17, in wrapper
result = func(*args, **kwargs)
File "/Users/admin/PycharmProjects/env_test/custom_decorator/log_decorator.py", line 27, in divide
return a/b, message
ZeroDivisionError: division by zero
(2.0, 'good')

缓存装饰器

缓存装饰器用于实现缓存,比如LRU Cache等。下面的例子将使用LRU Cache来缓存fibonacci函数的中间运行结果,从而加快函数运行时间,代码如下:

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
# -*- coding: utf-8 -*-
import time
from functools import lru_cache


def fibonacci(n):
if n <= 1:
return 1
else:
return fibonacci(n-1) + fibonacci(n-2)


@lru_cache(maxsize=5)
def cache_fibonacci(n):
if n <= 1:
return 1
else:
return cache_fibonacci(n-1) + cache_fibonacci(n-2)


if __name__ == '__main__':
t1 = time.time()
print(fibonacci(37))
t2 = time.time()
print(t2 - t1)
print(cache_fibonacci(37))
print(time.time() - t2)

执行结果:

1
2
3
4
39088169
3.7291252613067627
39088169
4.291534423828125e-05

retry装饰器

在工程项目中,尝尝会遇到数据库连接、HTTP连接等,这时一般都会加个retry(重连)机制,用于连接中断时发起重新连接,避免不必要的错误。Python中常见的retry模块有retry, tenacity等。

下面的示例代码将演示在断网情况下的HTTP重连:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# -*- coding: utf-8 -*-
import requests
from retry import retry
import logging

logging.basicConfig(level=logging.DEBUG,
format='%(asctime)s %(filename)s : %(levelname)s %(message)s')
logger = logging.getLogger()


@retry(exceptions=Exception, tries=5, delay=1, backoff=2, max_delay=30, logger=logger)
def make_requests():
url = "https://www.example.com/"
response = requests.get(url)
return response.content


if __name__ == '__main__':
print(make_requests())

执行结果:

1
2
3
4
5
6
7
8
9
2023-12-18 13:09:28,869  connectionpool.py : DEBUG  Starting new HTTPS connection (1): www.example.com:443
2023-12-18 13:09:28,870 api.py : WARNING HTTPSConnectionPool(host='www.example.com', port=443): Max retries exceeded with url: / (Caused by NewConnectionError('<urllib3.connection.HTTPSConnection object at 0x103959e10>: Failed to establish a new connection: [Errno 8] nodename nor servname provided, or not known')), retrying in 1 seconds...
2023-12-18 13:09:29,872 connectionpool.py : DEBUG Starting new HTTPS connection (1): www.example.com:443
2023-12-18 13:09:29,872 api.py : WARNING HTTPSConnectionPool(host='www.example.com', port=443): Max retries exceeded with url: / (Caused by NewConnectionError('<urllib3.connection.HTTPSConnection object at 0x1039590f0>: Failed to establish a new connection: [Errno 8] nodename nor servname provided, or not known')), retrying in 2 seconds...
2023-12-18 13:09:31,874 connectionpool.py : DEBUG Starting new HTTPS connection (1): www.example.com:443
2023-12-18 13:09:31,875 api.py : WARNING HTTPSConnectionPool(host='www.example.com', port=443): Max retries exceeded with url: / (Caused by NewConnectionError('<urllib3.connection.HTTPSConnection object at 0x103959210>: Failed to establish a new connection: [Errno 8] nodename nor servname provided, or not known')), retrying in 4 seconds...
2023-12-18 13:09:35,878 connectionpool.py : DEBUG Starting new HTTPS connection (1): www.example.com:443
2023-12-18 13:09:35,878 api.py : WARNING HTTPSConnectionPool(host='www.example.com', port=443): Max retries exceeded with url: / (Caused by NewConnectionError('<urllib3.connection.HTTPSConnection object at 0x103959510>: Failed to establish a new connection: [Errno 8] nodename nor servname provided, or not known')), retrying in 8 seconds...
2023-12-18 13:09:43,882 connectionpool.py : DEBUG Starting new HTTPS connection (1): www.example.com:443

其它装饰器

Python中还有许许多多的装饰器,使用装饰器不仅能使我们的代码简洁美观,而且非常方便实用,比如在Torch中,可以使用@torch.no_grad()装饰器来代替torch.no_grad()写法,非常方便,如下:

torch.no_grad() 的装饰器使用方法:

1
2
3
@torch.no_grad()
def eval():
...

总结

在Python中,装饰器是十分常见且好用的功能,实现起来也不困难,后续有机会讲仔细讲解装饰器的原理及其实现。

欢迎关注我的公众号NLP奇幻之旅,原创技术文章第一时间推送。

欢迎关注我的知识星球“自然语言处理奇幻之旅”,笔者正在努力构建自己的技术社区。


Python中常见的装饰器(decorator)
https://percent4.github.io/Python中常见的装饰器(decorator)/
作者
Jclian91
发布于
2024年1月11日
许可协议