typing.ParamSpec 如何保留被装饰函数的 *args / **kwargs 类型

发布时间 - 2026-01-31 00:00:00    点击率:
不能——ParamSpec仅记录参数结构“形状”,不保存args/kwargs的具体类型注解,P.args恒为tuple[object, ...],需用Concatenate显式拼接才能保留如args: float等类型信息。

ParamSpec 能不能原样保留 *args**kwargs 的类型?

不能直接保留——ParamSpec 本身不捕获 *args: P.args**kwargs: P.kwargs 的具体类型,它只记录参数结构的“形状”,不保存动态参数的实际注解。如果你写 def f(*args: int, **kwargs: str)P = ParamSpec('P') 绑定后,P.argstuple[object, ...]P.kwargsdict[str, Any],原始 intstr 信息就丢了。

Concatenate + 显式标注才能保留 *args 类型

要让装饰器把 *args: int 传下去,必须手动拆开参数结构,用 Concatenate 把固定参数和可变参数拼起来,并显式写出 *args 的类型。常见错误是只写 P,结果类型检查器认为 *args 是泛型占位符而非具体类型。

  • ParamSpec 适合转发签名但不关心 *args/**kwargs 具体类型(比如日志装饰器)
  • 若需保留 *args: int,定义装饰器时得用 Callable[Conc

    atenate[int, P], R]
    ,并让被装饰函数显式标注 *args: int
  • **kwargs 同理:用 Concatenate[Unpack[T], P](Python 3.12+)或配合 TypedDict 模拟强类型 **kwargs

实际例子:带类型感知的重试装饰器

下面这个装饰器能正确推导 f(x: str, *args: float, **kwargs: bool)*argsfloat**kwargsbool

from typing import Callable, TypeVar, ParamSpec, Concatenate, Unpack, TypedDict
import time

P = ParamSpec('P') R = TypeVar('R')

假设我们只关心 *args: float,其他保持原样

def retry( func: Callable[Concatenate[float, P], R] ) -> Callable[Concatenate[float, P], R]: def wrapper(*args: float, *kwargs: P.kwargs) -> R: for _ in range(3): try: return func(args, **kwargs) except Exception: time.sleep(1) raise RuntimeError("Failed after retries") return wrapper

使用时必须显式标注 *args / **kwargs 类型

def my_func(x: str, *args: float, **kwargs: bool) -> int: return len(x) + sum(int(a) for a in args)

wrapped = retry(my_func) # ✅ mypy 知道 wrapped 接收 *args: float, **kwargs: bool

为什么 P.args 总是 tuple[object, ...]

这是 ParamSpec 的设计限制:它抽象的是“调用时参数如何分组”,不是“每个参数的静态类型”。P.args 对应的是 *args 形参整体,而 Python 类型系统中 *args: T 的类型本质是 tuple[T, ...],但 P 不存储这个 T——它只存 tuple[object, ...] 作为占位。真正要恢复 T,只能靠 Concatenate 显式拼接,或用 Callable[[int, str, *tuple[float, ...]], None] 这种硬编码方式。

所以别指望 ParamSpec 自动推导出 *args 的元素类型;它最常被误用的地方,就是以为 P.args 能当 tuple[float, ...] 用。


# python  # 编码  # app  # ai  # 为什么  # Float  # Object  # 可变参数  # bool  # int  # 泛型  # 形参  # 的是  # 它只  # 不保存  # 这是  # 要让  # 而非  # 但不  # 绑定  # 或用  # 你写 


相关栏目: 【 网站优化151355 】 【 网络推广146373 】 【 网络技术251813 】 【 AI营销90571


相关推荐: 怎么用AI帮你设计一套个性化的手机App图标?  Laravel中间件起什么作用_Laravel Middleware请求生命周期与自定义详解  Laravel的契約(Contracts)是什么_深入理解Laravel Contracts与依赖倒置  如何在云主机上快速搭建网站?  Laravel如何实现RSS订阅源功能_Laravel动态生成网站XML格式订阅内容【教程】  如何自己制作一个网站链接,如何制作一个企业网站,建设网站的基本步骤有哪些?  Win11怎么设置默认图片查看器_Windows11照片应用关联设置  中山网站推广排名,中山信息港登录入口?  儿童网站界面设计图片,中国少年儿童教育网站-怎么去注册?  Win11任务栏卡死怎么办 Windows11任务栏无反应解决方法【教程】  Laravel Blade模板引擎语法_Laravel Blade布局继承用法  用v-html解决Vue.js渲染中html标签不被解析的问题  网站制作软件免费下载安装,有哪些免费下载的软件网站?  Laravel怎么实现支付功能_Laravel集成支付宝微信支付  Python图片处理进阶教程_Pillow滤镜与图像增强  Android利用动画实现背景逐渐变暗  C语言设计一个闪闪的圣诞树  网站制作价目表怎么做,珍爱网婚介费用多少?  Laravel Octane如何提升性能_使用Laravel Octane加速你的应用  Laravel中DTO是什么概念_在Laravel项目中使用数据传输对象(DTO)  谷歌Google入口永久地址_Google搜索引擎官网首页永久入口  Laravel如何创建自定义Artisan命令?(代码示例)  Laravel如何操作JSON类型的数据库字段?(Eloquent示例)  制作公司内部网站有哪些,内网如何建网站?  高防服务器租用如何选择配置与防御等级?  通义万相免费版怎么用_通义万相免费版使用方法详细指南【教程】  Laravel广播系统如何实现实时通信_Laravel Reverb与WebSockets实战教程  Swift开发中switch语句值绑定模式  网站制作大概多少钱一个,做一个平台网站大概多少钱?  微信公众帐号开发教程之图文消息全攻略  使用豆包 AI 辅助进行简单网页 HTML 结构设计  Laravel怎么使用Session存储数据_Laravel会话管理与自定义驱动配置【详解】  高性价比服务器租赁——企业级配置与24小时运维服务  EditPlus中的正则表达式实战(6)  Laravel怎么实现观察者模式Observer_Laravel模型事件监听与解耦开发【指南】  长沙做网站要多少钱,长沙国安网络怎么样?  千库网官网入口推荐 千库网设计创意平台入口  Laravel安装步骤详细教程_Laravel环境搭建指南  JavaScript如何实现类型判断_typeof和instanceof有什么区别  php结合redis实现高并发下的抢购、秒杀功能的实例  免费制作统计图的网站有哪些,如何看待现如今年轻人买房难的情况?  Laravel的HTTP客户端怎么用_Laravel HTTP Client发起API请求教程  INTERNET浏览器怎样恢复关闭标签页_INTERNET浏览器标签恢复快捷键与方法【指南】  如何打造高效商业网站?建站目的决定转化率  Laravel如何使用Facades(门面)及其工作原理_Laravel门面模式与底层机制  Laravel N+1查询问题如何解决_Eloquent预加载(Eager Loading)优化数据库查询  在centOS 7安装mysql 5.7的详细教程  网站广告牌制作方法,街上的广告牌,横幅,用PS还是其他软件做的?  HTML透明颜色代码怎么让图片透明_给img元素加透明色的技巧【方法】  如何快速重置建站主机并恢复默认配置?