如何在Python中避免重复传递关键字参数并保留类型提示?
Python 复用函数参数类型提示(类似TypeScript Parameters)
问题场景
我有一个类的公开方法train_model,内部需要调用另一个签名完全一致的self.model.train方法,目前只能手动逐个传递所有参数,非常繁琐。但因为是公开方法必须保留完整的类型提示,所以不想直接用**kwargs跳过类型检查。
原方法代码:
def train_model(self, out_dir='./out/', test_size=0.2, train_size=None, random_state=None, shuffle=True, stratify=None, epochs=DEFAULT_EPOCHS, batch_size=DEFAULT_BATCH_SIZE): ... # 手动传参繁琐且易出错 self.model.train(test_size=test_size, train_size=train_size, random_state=random_state, shuffle=shuffle, stratify=stratify, epochs=epochs, batch_size=batch_size)
我想要实现类似TypeScript中Parameters工具类型的效果,能直接复用目标函数的参数类型,比如TS里的写法:
function sum(a: number, b: number) { return a + b; } type SumParamsType = Parameters<typeof sum> // 后续可在其他地方复用该参数类型
我试过的两种失败方案
方案1:用TypeVar捕获参数类型
from typing import TypeVar T = TypeVar('T') def f1(a=1, b=2, c=3): return a+b+c # 试图用TypeVar捕获f1的参数类型,但无法实现 def f2(z=0, **kwargs: T): return z+f1(**kwargs) # 结果:T无法识别f1的具体参数类型,类型检查完全失效
方案2:直接读取函数annotations
def f1(a=1, b=2, c=3): return a+b+c def f2(z=0, **kwargs: f1.__annotations__['kwargs']): return z + f1(**kwargs) # 结果:kwargs被识别为Any类型,完全失去类型提示作用
可行解决方案
方法1:使用ParamSpec(推荐,Python 3.10+)
Python 3.10引入了typing.ParamSpec,专门用来捕获函数的参数类型规格,完美解决这个问题。低版本Python可以安装typing_extensions库兼容该特性。
示例代码:
from typing import ParamSpec, TypeVar from typing_extensions import Self # Python 3.11+可直接用typing.Self # 定义参数规格变量和返回值类型变量 P = ParamSpec('P') DEFAULT_EPOCHS = 10 DEFAULT_BATCH_SIZE = 32 class Model: def train(self, test_size: float = 0.2, train_size: float | None = None, random_state: int | None = None, shuffle: bool = True, stratify: any | None = None, epochs: int = DEFAULT_EPOCHS, batch_size: int = DEFAULT_BATCH_SIZE) -> None: # 模型训练逻辑 pass class Trainer: def __init__(self): self.model = Model() def train_model(self, out_dir: str = './out/', **kwargs: P.args) -> Self: # 处理out_dir相关逻辑 ... # 直接传递参数,类型检查工具会自动校验是否符合train方法的签名 self.model.train(**kwargs) return self
这样写的话,train_model的**kwargs会完全继承Model.train的参数类型提示,调用时传入不符合的参数会被mypy、Pyright等工具报错,同时无需手动维护重复的参数列表。
方法2:用TypedDict定义参数类型
如果不想依赖ParamSpec,可以手动把目标函数的参数定义成TypedDict,再在两个函数中复用这个字典类型:
from typing import TypedDict, Optional DEFAULT_EPOCHS = 10 DEFAULT_BATCH_SIZE = 32 # total=False表示所有字段都是可选的 class TrainParams(TypedDict, total=False): test_size: float train_size: Optional[float] random_state: Optional[int] shuffle: bool stratify: Optional[any] epochs: int batch_size: int class Model: def train(self, test_size: float = 0.2, train_size: Optional[float] = None, random_state: Optional[int] = None, shuffle: bool = True, stratify: Optional[any] = None, epochs: int = DEFAULT_EPOCHS, batch_size: int = DEFAULT_BATCH_SIZE) -> None: pass class Trainer: def __init__(self): self.model = Model() def train_model(self, out_dir: str = './out/', **kwargs: TrainParams) -> None: ... self.model.train(**kwargs)
这种方式需要手动维护TypedDict和函数签名的一致性,适合参数列表不常变动的场景。
内容的提问来源于stack exchange,提问作者link89
相关产品推荐
相关产品推荐

