You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何安全重载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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.13 06:09:53