为何Mypy对Numba @装饰器写法不报类型错,显式调用却报错?
测试脚本与问题现象
复现问题的测试代码如下:
import numba as nb @nb.njit def f1(x: int) -> int: return 2 * x def f2(x: int) -> int: return 2 * x f2 = nb.njit(f2) f1(1) f1(1.0) f2(1) f2(1.0) # error: Argument 1 to "f" has incompatible type "float"; expected "int"
对上述脚本执行mypy类型检查时会出现行为差异:
f1(1.0)不会触发任何类型报错f2(1.0)会抛出类型不兼容错误:error: Argument 1 to "f" has incompatible type "float"; expected "int"
两种写法在Python运行时逻辑完全等价,唯一区别是装饰器的使用形式:
f1采用PEP 318规范定义的标准@装饰器语法f2采用显式调用装饰器后重赋值的写法
根本原因
该差异和代码运行时行为无关,完全来自mypy静态类型检查阶段的推断逻辑差异:
- 处理
@nb.njit装饰的f1时,mypy会走装饰器语法的专属类型处理流程:不会先将f1的类型绑定为原函数的严格签名(x: int) -> int,而是直接将nb.njit的返回值类型作为f1的最终类型。numba提供给类型检查工具的类型存根中,nb.njit返回的JIT可调用对象(Dispatcher类型)的__call__方法做了泛型重载,允许传入所有和原参数类型兼容的数值类型——float可以被numba隐式转换为对应整型,属于合法输入,因此传入1.0时不会触发类型报错。 - 处理显式重赋值的
f2时,mypy走普通变量赋值的类型推断流程:- 首先解析
def f2(x: int) -> int的函数定义,此时f2的类型已经被初始化为严格的函数签名Callable[[int], int] - 随后校验
f2 = nb.njit(f2)的赋值合法性时,mypy只会检查右侧nb.njit(f2)的返回值是否兼容左侧f2的现有类型。JIT生成的Dispatcher本身是可调用对象,符合Callable[[int], int]的结构要求,因此mypy不会更新f2的类型为更宽松的Dispatcher类型,而是直接保留了f2最初的严格签名。 - 后续调用
f2(1.0)时,mypy按照严格的(x: int) -> int签名校验参数,自然会抛出float与int不匹配的错误。
- 首先解析
内容的提问来源于stack exchange,提问作者norok2
相关产品推荐
相关产品推荐

