如何安全重载Callable返回值类型提示,解决type不支持ParamSpec问题?
问题描述
我需要安全地重载Callable返回值的类型提示并进行区分,但遇到一个问题:type类型不支持ParamSpec,无法像Callable那样约束args和kwargs参数。
当前代码如下:
from collections.abc import Callable import typing_extensions as t P = t.ParamSpec("P") T_co = t.TypeVar("T_co", covariant=True) class ExpectCallable(t.Generic[P, T_co]): ... class ExpectType(ExpectCallable[P, T_co]): ... @t.overload def expect( v: type[T_co], /, *args: P.args, **kwargs: P.kwargs ) -> ExpectType[P, T_co]: ... @t.overload def expect( v: Callable[P, T_co], /, *args: P.args, **kwargs: P.kwargs ) -> ExpectCallable[P, T_co]: ... def expect( v: type[T_co] | Callable[P, T_co], /, *args: P.args, **kwargs: P.kwargs ) -> ExpectType[P, T_co] | ExpectCallable[P, T_co]: return ExpectType() if isinstance(v, type) else ExpectCallable() class A: def __init__(self, inp: str, /) -> None: ... def fn(inp: str, /) -> None: ... t.assert_type(expect(A), ExpectType[[], A]) # Shouldn't be allowed t.assert_type(expect(fn, "inp"), ExpectCallable[[str], None])
可以看到,t.assert_type(expect(A), ExpectType[[], A])没有报错,但A的构造函数需要传入str参数。我期望expect(A)也能像expect(fn, "inp")一样强制要求传入str参数。我知道这是因为A是type类型,无法用P来约束P.args和P.kwargs,但有没有类型安全的实现方式?
注:示例中未使用args和**kwargs是为了简化,它们会在Expect类中实际使用。*
解决方案
核心思路是利用Python类本身可被视为构造函数的Callable特性——调用类实例化对象等价于调用其__init__方法,因此可以通过Callable关联类的构造签名,再配合重载实现类型约束。
方法一:使用ParamSpecArgs和ParamSpecKwargs绑定类构造签名
from collections.abc import Callable import typing_extensions as t P = t.ParamSpec("P") T_co = t.TypeVar("T_co", covariant=True) # 绑定到任意返回对象的Callable,覆盖类的构造行为 C = t.TypeVar("C", bound=Callable[..., object]) class ExpectCallable(t.Generic[P, T_co]): ... class ExpectType(t.Generic[P, T_co]): ... @t.overload def expect(v: C, /, *args: t.ParamSpecArgs[C], **kwargs: t.ParamSpecKwargs[C]) -> ExpectType[t.ParamSpecArgs[C], t.ReturnType[C]]: ... @t.overload def expect(v: Callable[P, T_co], /, *args: P.args, **kwargs: P.kwargs) -> ExpectCallable[P, T_co]: ... def expect(v: C | Callable[P, T_co], /, *args: t.Unpack[t.ParamSpecArgs[C]] | P.args, **kwargs: t.ParamSpecKwargs[C] | P.kwargs) -> ExpectType[t.ParamSpecArgs[C], t.ReturnType[C]] | ExpectCallable[P, T_co]: return ExpectType() if isinstance(v, type) else ExpectCallable() class A: def __init__(self, inp: str, /) -> None: ... def fn(inp: str, /) -> None: ... # 触发类型错误:缺少构造参数inp,符合预期 t.assert_type(expect(A), ExpectType[[str], A]) # 正常通过 t.assert_type(expect(A, "test"), ExpectType[[str], A]) # 正常通过 t.assert_type(expect(fn, "inp"), ExpectCallable[[str], None])
方法二:新增重载覆盖类的构造Callable约束
from collections.abc import Callable import typing_extensions as t P = t.ParamSpec("P") T_co = t.TypeVar("T_co", covariant=True) class ExpectCallable(t.Generic[P, T_co]): ... class ExpectType(t.Generic[P, T_co]): ... @t.overload def expect(v: type[T_co], /, *args: P.args, **kwargs: P.kwargs) -> ExpectType[P, T_co]: ... @t.overload def expect(v: Callable[P, T_co], /, *args: P.args, **kwargs: P.kwargs) -> ExpectCallable[P, T_co]: ... # 新增重载:将类视为构造Callable,约束参数与返回类型 @t.overload def expect(v: Callable[P, T_co], /, *args: P.args, **kwargs: P.kwargs) -> ExpectType[P, T_co]: ... def expect(v: type[T_co] | Callable[P, T_co], /, *args: P.args, **kwargs: P.kwargs) -> ExpectType[P, T_co] | ExpectCallable[P, T_co]: return ExpectType() if isinstance(v, type) else ExpectCallable() class A: def __init__(self, inp: str, /) -> None: ... def fn(inp: str, /) -> None: ... # 触发类型错误:缺少str参数,符合预期 t.assert_type(expect(A), ExpectType[[str], A]) # 正常通过 t.assert_type(expect(A, "test"), ExpectType[[str], A]) t.assert_type(expect(fn, "inp"), ExpectCallable[[str], None])
两种方法都能实现类型安全的约束,确保调用expect(A)时必须传入构造所需的参数,同时区分返回ExpectType和ExpectCallable类型。
内容的提问来源于stack exchange,提问作者Nelson Yeung
相关产品推荐
相关产品推荐

