如何用mypy类型检查强制可调用类型签名?多签名参数函数校验问题
刚好我之前折腾过mypy的可调用类型约束,来给你捋清楚怎么解决这两个问题👇
解决mypy约束多签名可调用类型的问题
一、为什么isinstance(x, TYPE_A)会报错?
首先得明确:你定义的TYPE_A、TYPE_B如果是用Callable写的类型别名(比如TYPE_A = Callable[[int], str]),这些都是静态类型提示,在Python运行时根本不存在对应的类。而isinstance是用来检查运行时对象的实际类型的,所以mypy会直接报错,说你传给isinstance的第二个参数不是一个合法的运行时类型。
那该怎么正确判断呢?
二、用类型守卫结合运行时签名检查
你提到的用inspect.signature判断参数个数的思路是对的,但得配合mypy的**类型守卫(Type Guard)**功能,让静态检查能识别你运行时判断后的类型。
步骤1:先明确你的可调用类型
先把两种签名的类型别名定义清楚:
from typing import Callable, TypeGuard import inspect # 定义两种目标可调用类型 TYPE_A = Callable[[int], str] # 接收1个int参数,返回str TYPE_B = Callable[[int, str], bool] # 接收int+str参数,返回bool
步骤2:编写类型守卫函数
类型守卫函数的作用是告诉mypy:当这个函数返回True时,输入的对象肯定属于某个特定类型。这样就能把运行时的签名检查和静态类型检查打通:
def is_type_a(x: callable) -> TypeGuard[TYPE_A]: sig = inspect.signature(x) params = list(sig.parameters.values()) # 这里可以根据需求调整检查逻辑:比如只看参数个数,或者严格检查注解类型 return len(params) == 1 and params[0].annotation is int def is_type_b(x: callable) -> TypeGuard[TYPE_B]: sig = inspect.signature(x) params = list(sig.parameters.values()) return len(params) == 2 and params[0].annotation is int and params[1].annotation is str
如果你的函数参数没有显式加类型注解,那params[0].annotation会是inspect.Parameter.empty,这时候你只需要判断参数个数就行,不用检查注解。
步骤3:在业务函数中使用类型守卫
现在在你的my_func里,用这些守卫函数判断后,mypy会自动窄化变量类型,不会再报错:
def my_func(x: TYPE_A | TYPE_B) -> None: if is_type_a(x): # mypy在这里明确知道x是TYPE_A类型,调用时会检查参数是否符合 result = x(123) print(f"TYPE_A执行结果: {result}") elif is_type_b(x): # 同理,这里x被识别为TYPE_B类型 result = x(123, "测试字符串") print(f"TYPE_B执行结果: {result}") else: # 兜底处理,防止运行时出现不符合两种签名的函数 raise ValueError("x必须是TYPE_A或TYPE_B类型的可调用对象")
三、额外小提示
- 如果你用的是Python 3.9及以下版本,联合类型要写成
Union[TYPE_A, TYPE_B],3.10+可以直接用TYPE_A | TYPE_B - 要是不想严格检查参数注解,只看参数个数,把守卫函数里的注解检查去掉就行,更灵活
- 开启mypy的
strict模式,可以确保所有分支都被覆盖,避免遗漏情况
内容的提问来源于stack exchange,提问作者user1848861




