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

如何为instance_check实现TypeGuard式类型收窄(.pyi兼容)

如何为抛出异常的instance_check函数编写.pyi签名以实现类型收窄?

我正在为一个无类型的第三方库编写.pyi类型存根文件,其中有一个instance_check()函数——它是isinstance()的包装器,当实例检查失败时会抛出异常,而非返回False。我需要为它编写正确的类型注解,使得调用该函数后,第一个参数的类型能被类型检查器自动收窄,效果等同于assert isinstance(...)。

举个期望的效果示例:

class Foo:
    pass

bar: Any = ...
instance_check(bar, Foo)
reveal_type(bar)  # 应显示为`Foo`

# 等效于以下代码的类型收窄效果:
baz: Any = ...
assert isinstance(baz, Foo)
reveal_type(baz)  # -> Foo

isinstance()本身的类型签名通过TypeGuard[T]实现了这种收窄:

def isinstance[T](x: Any, t: type[T]) -> TypeGuard[T]: ...

我的问题是:有没有完全兼容.pyi文件的方式,为instance_check编写签名,让后续代码中第一个参数的类型总能被正确收窄?


为什么不能直接用TypeGuard[T] | NoReturn?

我之前尝试过这种写法,但它不符合规范:

def instance_check[T](item: Any, instance: type[T]) -> TypeGuard[T] | NoReturn: ...

原因是TypeGuard的规范要求函数必须返回布尔值,而instance_check的实际实现返回None,且我无法修改原库的实现让它返回bool。

为什么返回None的签名不行?

如果简单地写返回None,类型检查器不会进行任何类型收窄:

def instance_check[T](item: Any, instance: type[T]) -> None: ...

实际使用时的类型提示不符合预期:

class Foo:
    pass

x: Any = ...
instance_check(x, Foo)
reveal_type(x)  # -> Any

与同类问题的区别

有类似问题是关于为“类型转换并在失败时抛出异常”的函数写注解,但我的场景不同:

  • 目标函数不返回转换后的新值,只是验证原参数类型并抛出异常
  • 我只能修改.pyi存根文件,无法改动原库的函数实现或签名
  • 类型检查器不会对.pyi文件做类型推断,必须完全依赖显式注解

解决方案:在.pyi中使用TypeGuard[T]注解

尽管instance_check实际返回None,但在.pyi存根文件中可以合法地声明它返回TypeGuard[T]。类型检查器会优先遵循存根中的注解语义,而忽略运行时的返回值:

from typing import Any, TypeGuard, TypeVar

T = TypeVar("T")

def instance_check(item: Any, instance: type[T]) -> TypeGuard[T]: ...

原理

TypeGuard[T]的核心语义是:如果函数正常返回(没有抛出异常),则传入的参数必然符合类型T。对于instance_check这种“检查失败就抛异常”的函数,只要它正常执行完毕,就意味着类型检查通过,因此类型检查器会自动将第一个参数的类型收窄为T,完全符合我们的需求。

验证效果

使用上述存根注解后,之前的示例代码中:

bar: Any = ...
instance_check(bar, Foo)
reveal_type(bar)  # 类型检查器(如mypy、pyright)会显示为`Foo`

内容的提问来源于stack exchange,提问作者Benjie

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.19 12:10:05