如何为Python中Foo类的fn1、fn2和kwargs添加正确类型注解?
如何为Python中Foo类的参数添加类型注解以约束kwargs同时满足多个函数要求
问题描述
我想给下面代码里Foo类的fn1、fn2和**kwargs添加类型注解:
class Foo: def __init__(self, fn1, fn2, **kwargs): self.res1 = fn1(**kwargs) self.res2 = fn2(**kwargs)
需要让kwargs同时满足fn1和fn2的输入参数要求,避免出现这种错误场景:
# 因kwargs缺少key y,fn2执行失败 Foo(fn1=lambda x: x+1, fn2=lambda x,y:x+y, x=1)
想问在Python 3.11或3.12版本中,能不能实现正确的类型注解?
之前处理单参数情况时,我用typing.TypeVar实现的代码如下:
from typing import Callable, TypeVar T = TypeVar('T') class Foo: def __init__(self, fn1: Callable[[T], T], fn2: Callable[[T], T], x: T): self.res1 = fn1(x) self.res2 = fn2(x) # mypy类型检查失败 Foo(lambda x, y: x+1, lambda y: y*2, x=5) # mypy类型检查通过 Foo(lambda x: x+x, lambda y: y*2, x='a') Foo(lambda x: x+x, lambda y: y*2, x=3.14)
运行mypy file.py的输出:
file.py:13: error: Cannot infer type of lambda [misc] file.py:13: error: Argument 1 to "Foo" has incompatible type "Callable[[Any, Any], Any]"; expected "Callable[[int], int]" [arg-type] Found 2 errors in 1 file (checked 1 source file)
解决方案
在Python 3.11/3.12中,可以通过**typing.ParamSpec和TypeVar结合**来实现这种对kwargs的约束,确保传入的关键字参数同时满足fn1和fn2的参数要求。
具体实现代码
from typing import Callable, ParamSpec, TypeVar P = ParamSpec('P') R1 = TypeVar('R1') R2 = TypeVar('R2') class Foo: def __init__( self, fn1: Callable[P, R1], fn2: Callable[P, R2], **kwargs: P.kwargs, ): self.res1 = fn1(**kwargs) self.res2 = fn2(**kwargs)
类型检查效果验证
测试错误场景:
# mypy会报错:缺少参数y Foo(fn1=lambda x: x+1, fn2=lambda x,y:x+y, x=1)
测试合法场景:
# 检查通过,kwargs同时满足两个函数的参数要求 Foo(fn1=lambda x: x+1, fn2=lambda x,y:x+y, x=1, y=2) # 检查通过,两个函数仅需x参数 Foo(fn1=lambda x: x+1, fn2=lambda x:x*2, x=3)
原理说明
ParamSpec('P')用于捕获函数的参数签名(包括位置参数和关键字参数)Callable[P, R1]表示fn1的参数签名必须匹配P,返回值类型为R1;fn2同理**kwargs: P.kwargs明确要求传入的kwargs必须完全匹配P所捕获的关键字参数集合,这样就能保证kwargs同时满足fn1和fn2的参数要求
注意:这个特性需要使用支持ParamSpec的类型检查器,比如mypy 0.910+或者pyright,Python 3.10及以上版本原生支持ParamSpec(从typing模块导入)。
内容的提问来源于stack exchange,提问作者ShmulikA
相关产品推荐
相关产品推荐

