requests.Session子类新增参数时如何配置类型提示解决mypy报错
以下代码运行时逻辑完全正常,但使用mypy执行静态类型检查时会抛出类型错误:
class NamePrintingSession(requests.Session): def request(self, method, url, name=None, **kwargs): print(name) return super().request(method, url, **kwargs) nps = NamePrintingSession() nps.get("http://www.google.com", name="wooo") # error: Unexpected keyword argument "name" for "get" of "Session"
报错原因明确:requests库的get()、post()等快捷方法原生实现虽然接收**kwargs做参数透传,但库自带的类型存根(stubs)固定了这些方法的参数约束,子类重写request()方法新增的参数,不会自动同步到从父类继承的其他快捷方法的类型定义中。
核心诉求:找到可行的类型配置方案,让子类继承来的get、post、delete等方法,在完整保留父类所有原有参数提示、文档关联的基础上,新增对name参数的类型支持;优先选择不需要手动重复声明每个方法全量参数的实现。
方案1:ParamSpec类型包装(最优,无冗余代码)
这个方案仅在类型检查层面对继承来的方法做参数扩展,运行时完全复用父类原生逻辑,不需要重写任何快捷方法,也能完整保留原有方法的参数提示、文档跳转能力。
依赖说明
- Python 3.10及以上版本:直接使用标准库
typing模块提供的ParamSpec、Concatenate、TypeVar即可 - Python 3.10以下版本:安装
typing_extensions依赖包,从该包导入上述类型对象即可
实现代码
from typing import Callable, Concatenate, ParamSpec, TypeVar import requests P = ParamSpec("P") R = TypeVar("R") def _append_name_arg(func: Callable[P, R]) -> Callable[Concatenate[str | None, P], R]: """仅用于类型标注的辅助函数,运行时直接返回原方法无额外逻辑""" return func class NamePrintingSession(requests.Session): # 给需要支持name参数的快捷方法绑定扩展后的类型 get = _append_name_arg(requests.Session.get) post = _append_name_arg(requests.Session.post) delete = _append_name_arg(requests.Session.delete) put = _append_name_arg(requests.Session.put) patch = _append_name_arg(requests.Session.patch) head = _append_name_arg(requests.Session.head) options = _append_name_arg(requests.Session.options) def request(self, method, url, name: str | None = None, **kwargs): print(name) return super().request(method, url, **kwargs)
方案优势
- 代码冗余度极低:每个快捷方法只需要一行类型绑定代码,不需要复制父类方法的几十个参数定义
- 无运行时损耗:辅助函数直接返回原方法对象,完全复用requests原生实现,不会引入额外逻辑
- 类型提示完整:原有方法的所有参数提示、参数类型校验、文档跳转链接完全保留,仅额外新增了可选的
name关键字参数支持 - 兼容性好:mypy、pyright等主流静态类型检查工具都能正确识别扩展后的方法签名
注意事项
调用时name参数必须以关键字形式传入,这也符合这类HTTP请求方法的传参习惯,不会出现参数位置错位的问题。
方案2:本地类型存根覆盖(适合无代码侵入场景)
如果不想在业务代码中加入类型相关的辅助逻辑,可以在项目配置的类型存根目录下,为NamePrintingSession编写单独的.pyi类型存根文件,声明对应快捷方法追加name参数即可。
这种方案业务代码完全无侵入,但需要单独维护存根文件,后续如果需要扩展其他参数需要同步修改存根,维护成本略高于方案1。
不推荐方案
不建议手动重写每个get/post等快捷方法、在方法内部调用父类实现的写法:这种方式需要手动复制每个方法的全量参数签名,代码冗余度极高,后续requests版本更新调整方法参数时,还需要手动同步修改所有重写的方法,维护成本很高。
内容的提问来源于stack exchange,提问作者Cyberwiz

