Python装饰器:如何同时展示包装函数额外参数的签名及装饰器与被装饰函数的文档字符串
这个问题确实是Python装饰器开发中常见的痛点——functools.wraps只能单向复制原函数的元信息,没法同时兼顾装饰器新增的参数和文档。下面给你一套原生Python的解决方案,同时也推荐更省心的第三方库选项:
一、让IDE识别装饰器新增的参数(签名问题)
IDE的参数提示依赖Python的inspect模块识别函数的__signature__属性,functools.wraps只会复制原函数的签名,所以我们需要手动构造一个合并后的签名:
from functools import wraps import inspect def my_decorator(func): @wraps(func) def wrapper(*args, fancy_processing=False, **kwargs): """Some nice docstring :param fancy_processing: If set, does fancy processing before executing the function """ output = func(*args, **kwargs) if fancy_processing: output += 1 # 自定义处理逻辑 return output # 1. 获取原函数和包装函数的签名 original_sig = inspect.signature(func) wrapper_sig = inspect.signature(wrapper) # 2. 提取装饰器新增的关键字参数(这里是fancy_processing) extra_param = [p for p in wrapper_sig.parameters.values() if p.name == "fancy_processing"][0] # 3. 合并原函数参数和新增参数,构造新签名 new_params = list(original_sig.parameters.values()) + [extra_param] # 自动补充**kwargs(如果原函数没有的话) if "kwargs" not in [p.name for p in new_params]: kwargs_param = inspect.Parameter( "kwargs", kind=inspect.Parameter.VAR_KEYWORD, annotation=dict ) new_params.append(kwargs_param) new_sig = original_sig.replace(parameters=new_params) # 4. 赋值给包装函数的__signature__属性,IDE会识别这个属性 wrapper.__signature__ = new_sig # --- 下面处理文档字符串 --- # 二、规范合并原函数和装饰器的文档字符串 original_doc = func.__doc__ or "" wrapper_doc = wrapper.__doc__ or "" # 针对你使用的reStructuredText风格文档,精准插入参数说明 if ":param fancy_processing:" in wrapper_doc: # 拆分原文档的参数部分和返回部分 doc_parts = original_doc.split(":returns:") if len(doc_parts) == 2: params_section, returns_section = doc_parts # 提取装饰器的参数说明文本 fancy_doc = wrapper_doc.split(":param fancy_processing:")[1].split("\n")[0].strip() # 合并文档 merged_doc = f"{params_section}:param fancy_processing: {fancy_doc}\n:returns:{returns_section}" else: # 如果原文档没有返回部分,直接追加装饰器的参数说明 merged_doc = f"{original_doc}\n\n:param fancy_processing: {fancy_doc}" else: merged_doc = f"{original_doc}\n\n{wrapper_doc}" wrapper.__doc__ = merged_doc return wrapper
二、测试效果
用这个装饰器修饰你的foo函数:
@my_decorator def foo(important_param, other_important_param=4): """This is a well-written docstring. :param important_param: Super important parameter :param other_important_param: Other super important parameter :returns: Something incredibly useful """ print(important_param) return other_important_param + 1
现在在PyCharm或Jupyter里:
- 调用
foo(时,会看到参数提示包含important_param、other_important_param和fancy_processing - 查看
foo.__doc__或使用文档提示时,会同时显示原函数和装饰器的参数说明
更省心的第三方库选项
如果不想手动处理这些细节,可以用wrapt库(Python装饰器的工业级解决方案),它能更优雅地处理签名和文档的合并:
import wrapt @wrapt.decorator def my_decorator(wrapped, instance, args, kwargs): fancy_processing = kwargs.pop("fancy_processing", False) output = wrapped(*args, **kwargs) if fancy_processing: output += 1 return output # 手动合并文档字符串(wrapt默认会保留原函数的文档,你可以追加装饰器的说明) my_decorator.__doc__ = """Some nice docstring :param fancy_processing: If set, does fancy processing before executing the function """ @my_decorator def foo(important_param, other_important_param=4): """This is a well-written docstring. :param important_param: Super important parameter :param other_important_param: Other super important parameter :returns: Something incredibly useful """ print(important_param) return other_important_param + 1 # 签名处理仍可复用之前的inspect方法,或者使用wrapt的辅助工具简化流程
内容的提问来源于stack exchange,提问作者emilaz
相关产品推荐
相关产品推荐

