本文将会介绍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 import timefrom functools import wrapsdef 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 ): time.sleep(n) return nif __name__ == '__main__' : t = time_sleep(3 ) print (t)
执行结果:
1 2 Execution time of time_sleep: 3 .0027120113372803 seconds3
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 from functools import wrapsdef 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 from functools import wrapsimport 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, messageif __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 import timefrom functools import lru_cachedef 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 .291534423828125 e-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 import requestsfrom 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.contentif __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奇幻之旅 ,原创技术文章第一时间推送。
欢迎关注我的知识星球“自然语言处理奇幻之旅 ”,笔者正在努力构建自己的技术社区。