Python装饰器类型注解优化:如何让装饰后函数显示正确签名与文档?
解决Python装饰器的类型注解与签名显示问题
核心解决方案
要同时实现正确的签名显示、文档字符串继承以及新增参数的类型校验,需要结合functools.wraps和typing模块的ParamSpec/TypeVar来精准定义装饰器的类型,同时完善参数校验逻辑。
具体实现步骤
1. 导入必要模块
from functools import wraps from typing import ParamSpec, TypeVar, Callable, Optional # 假设你已定义以下业务类型 class DashboardAPI: """Meraki Dashboard API 实例类型""" pass class MerakiKey: """Meraki API 密钥类型""" pass
2. 定义参数与返回类型变量
用ParamSpec保留原函数的参数结构,TypeVar保留返回类型,确保装饰器能适配任意符合结构的函数:
P = ParamSpec("P") # 原函数的参数集合 RT = TypeVar("RT") # 原函数的返回类型
3. 实现带类型注解的装饰器
def meraki_dashboard_setup(func: Callable[P, RT]) -> Callable[[Optional[DashboardAPI], Optional[MerakiKey], *P.args, **P.kwargs], RT]: @wraps(func) # 继承原函数的文档字符串、名称等元数据 def wrapper( dashboard: Optional[DashboardAPI] = None, meraki_key: Optional[MerakiKey] = None, *args: P.args, **kwargs: P.kwargs ) -> RT: # 运行时强制参数互斥校验 if dashboard is not None and meraki_key is not None: raise ValueError("不能同时提供dashboard和meraki_key参数") if dashboard is None and meraki_key is None: raise ValueError("必须提供dashboard或meraki_key中的一个") # 根据meraki_key创建DashboardAPI实例 if meraki_key is not None: dashboard = DashboardAPI() # 替换为你的实际初始化逻辑 # 调用原函数并传递处理后的dashboard return func(dashboard, *args, **kwargs) return wrapper
4. 使用装饰器
@meraki_dashboard_setup def get_organization_networks(dashboard: DashboardAPI, org_id: str) -> list: """获取指定组织下的所有网络列表""" # 你的业务逻辑实现 return []
效果说明
- 签名显示:IDE会正确识别装饰后的函数签名为
get_organization_networks(dashboard: Optional[DashboardAPI] = None, meraki_key: Optional[MerakiKey] = None, org_id: str) -> list,而非模糊的_Wrapped类型。 - 文档继承:通过
@wraps(func),原函数的文档字符串会被完整继承,调用时能正常查看。 - 类型校验:IDE会对
dashboard、meraki_key以及原函数的参数进行类型校验,运行时也会强制参数互斥规则。
版本兼容提示
如果使用Python 3.9及以下版本,需要从typing_extensions导入ParamSpec和Concatenate,并调整装饰器的返回类型注解为:
from typing_extensions import Concatenate def meraki_dashboard_setup(func: Callable[P, RT]) -> Callable[Concatenate[Optional[DashboardAPI], Optional[MerakiKey], P], RT]: # 内部逻辑不变 ...
内容的提问来源于stack exchange,提问作者Eddysanoli
相关产品推荐
相关产品推荐

