- functools.wraps
- functools.lru_cache(待补充)
- functools.total_ordering(待补充)
- functools.total_ordering(待补充)
- functools.singledispatch(待补充)
- functools.partial(待补充)
- functools.partialmethod(待补充)
- functools.reduce(待补充)
functools.wraps
是一个 Python 的装饰器工厂函数,用于更新一个函数对象,将另一个函数对象的元信息(如名称、文档字符串、注解和模块)复制到它上面。这通常用于创建装饰器,以确保被装饰的函数在装饰之后仍然保持其原始的元信息。
当你创建一个装饰器时,你通常会定义一个接受函数作为参数的函数,并返回一个新的函数对象。这会导致原始函数的元信息(如函数名)丢失,因为返回的是一个全新的函数对象。使用 functools.wraps
可以帮助解决这个问题。
先看一个例子
1 2 3 4 5 6 7 8 9 10 11 12
| def hello(): "print a well-known message" print("hello world") hello() > hello world
hello.__doc__ > 'print a well-known message'
hello.__name__ > 'hello'
|
接着我们构建一个装饰器并使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| def noop(func): def noop_wrapper(): "this is a decorator" return func() return noop_wrapper
@noop def hello(): "print a well-known message" print("hello world") hello() > hello world
hello.__doc__ > 'this is a decorator'
hello.__name__ > 'noop_wrapper'
|
可以发现,在套用装饰器的时候,函数的名称和文档都被修改成了装饰器的了
我们有两种方法可以修改回来,第二种方法就是使用functools
方法一:对__name__
和__doc__
重新赋值
对__name__
和__doc__
重新赋值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| def noop(func): def noop_wrapper(): "this is a decorator" return func() noop_wrapper.__name__ = func.__name__ noop_wrapper.__doc__ = func.__doc__ return noop_wrapper
@noop def hello(): "print a well-known message" print("hello world") hello() > hello world
hello.__doc__ > 'print a well-known message'
hello.__name__ > 'hello'
|
使用functools.wraps
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| from functools import wraps
def noop(func): @wraps(func) def noop_wrapper(): "this is a decorator" return f() return noop_wrapper
@noop def hello(): "print a well-known message" print("hello world") hello() > hello world
hello.__doc__ > 'print a well-known message'
hello.__name__ > 'hello'
|
实用性例子
下面是一个日志模块,其他程序套用日志模块来进行日志输出
先来看下大致的目录结构
1 2 3 4 5 6 7 8 9 10
| ├── logs │ └── __init__.py ├── testcase │ ├── __pycache__ │ ├── demo │ └── test_logger_demo.py └── utils ├── __init__.py ├── __pycache__ └── log_manager.py
|
log_manager.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 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
| from loguru import logger from time import strftime import os, sys from functools import wraps import time
class LoggerManager:
def __init__(self): self.logger = logger logger.remove() filename = strftime('%Y%m%d-%H%M%S') log_file_path = os.path.join('../../logs/', filename + '.log') print(log_file_path) log_format = '<green>{time: YYYY-MM-DD HH:mm:ss.SSS}</green> {level} {message}' level_ = 'DEBUG' rotation_ = '5MB'
""" :enqueue=True 是否使用队列异步地将日志消息写入文件, 日志消息会被排入队列中,然后由后台线程或进程异步地写入文件。 这样可以提高日志记录的性能,避免阻塞主线程。 :backtrace=True 这个参数用于指定是否记录追溯信息 日志中将包含追溯信息,帮助你追踪日志消息的来源 :diagnose=True 这个参数指定是否在日志文件中包含诊断信息, 例如记录日志消息时的函数调用栈等。这对于排查日志问题很有用。 :rotation=rotation_ 这个参数用于指定日志文件的轮转策略 """ self.logger.add(log_file_path, enqueue=True, backtrace=True, diagnose=True, encoding='UTF-8', rotation=rotation_)
""" 标准错误流 (sys.stderr): 用于输出错误信息和警告,通常用于指示程序运行中的问题 """ self.logger.add(sys.stderr, format=log_format, enqueue=True, backtrace=True, diagnose=True, colorize=True, level=level_)
def runtime_logger(self, func): def wrapper(*args, **kwargs): self.logger.info(f"{func.__name__} 当前开始执行")
now1 = time.time()
try: func(*args, **kwargs) except Exception as e: self.logger.error(f"{func.__name__} 当前用例执行失败,失败的原因是: {e}") now2 = time.time() self.logger.success(f"{func.__name__} 当前执行成功,耗时:{now2 - now1}ms")
return func
return wrapper
def runtime_logger_class(self, cls): for attr_name in dir(cls): if attr_name in dir(cls): if attr_name.startswith('test_') and callable(getattr(cls, attr_name)): setattr(cls, attr_name, self.runtime_logger(getattr(cls, attr_name))) return cls
my_logger = LoggerManager()
|
test_logger_demo.py
1 2 3 4 5 6 7 8 9 10 11 12 13
| import pytest from utils.log_manager import my_logger
class TestDemo:
@my_logger.runtime_logger def test_01_demo(self): my_logger.logger.info("这是一条日志")
if __name__ == '__main__': pytest.main()
|
如果不使用functools.wraps
用例的__name__
就会被修改成wrapper
,这是我们不希望看到的
修改前:
1 2 3 4
| ========================= 1 passed, 1 warning in 0.03s ========================= 2024-03-10 23:12:09.733 INFO wrapper 当前开始执行 2024-03-10 23:12:09.733 INFO 这是一条日志 2024-03-10 23:12:09.733 SUCCESS wrapper 当前执行成功,耗时:0.00012969970703125ms
|
修改后:
1 2 3 4
| ========================= 1 passed, 1 warning in 0.03s ========================= 2024-03-10 23:12:09.733 INFO test_01_demo 当前开始执行 2024-03-10 23:12:09.733 INFO 这是一条日志 2024-03-10 23:12:09.733 SUCCESS test_01_demo 当前执行成功,耗时:0.00012969970703125ms
|