如何为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

