返回

Mypy 与 NumPy 类型检查:`np.float_` 为什么会报错?

python

Mypy 与 NumPy 别名: 当 np.float_np.float64 不符合预期

在使用 Mypy 对 NumPy 代码进行类型检查时,你可能会遇到 np.float_np.float64 表现不符合预期的情况,即使 NumPy 文档表明它们是等价的。这篇文章将深入探讨这一现象,解释其背后的原因,并提供解决方法,帮助你编写类型安全的 NumPy 代码。

问题浮现

让我们从一个常见的场景开始。假设你编写了一个函数:

import numpy as np
from numpy.typing import NDArray

def print_max(arr: NDArray[np.float32]) -> None:
    print(f"arr.max() = {arr.max()}")

这个函数接受一个 np.float32 类型的 NumPy 数组,并打印其最大值。接下来,你使用不同类型的数组调用此函数:

a = np.ones((2,3), dtype=np.float_)
b = np.ones((2,3), dtype=np.float64)
c = np.ones((2,3), dtype=np.double)
d = np.ones((2,3), dtype=np.float32)

print_max(a)
print_max(b)
print_max(c)
print_max(d)

当你运行 mypy 进行类型检查时,可能会惊讶地发现,只有 print_max(d) 通过了检查。 使用 np.float_np.float64np.double 定义的数组 abc 都引发了类型错误。

造成这种情况的原因是 Mypy 对 NumPy 别名的处理方式。 np.float_ 在 Mypy 中被视为一个特殊类型,它表示平台相关的默认浮点数类型。在大多数平台上,这个默认类型确实是 np.float64,但 Mypy 并不会将其自动识别为 np.float32

解决方案

我们可以通过以下几种方法解决这个问题:

  1. 明确指定类型: 最直接的方法是避免使用 np.float_,直接使用 np.float64np.float32 明确指定所需的类型。

  2. 使用类型转换: 如果无法避免使用 np.float_,可以使用 astype 方法将数组转换为所需的类型:

    print_max(a.astype(np.float32)) 
    
  3. 调整 Mypy 配置: 可以通过修改 Mypy 配置文件,强制将 np.float_ 识别为 np.float64。但这种做法可能会导致代码在其他平台上出现问题,因此并不推荐。

最佳实践

为了编写更健壮的 NumPy 代码,建议遵循以下最佳实践:

  • 优先使用明确的类型,如 np.float64np.float32, 避免使用 np.float_ 等平台相关的类型别名。
  • 使用 mypy 进行类型检查,尽早发现潜在的类型错误。
  • 查阅 NumPy 和 Mypy 的官方文档,了解更多关于类型提示和类型检查的信息。

通过遵循以上建议,你可以编写出更易维护的 NumPy 代码,并充分利用 Mypy 的强大功能确保代码的类型安全。

常见问题解答

  1. 为什么 NumPy 会使用 np.float_ 这样的别名?

    np.float_ 的目的是提供一个平台无关的方式来表示默认的浮点数类型。然而,在进行类型检查时,这种平台相关性反而成为了一个障碍。

  2. 除了 np.float_ ,还有哪些 NumPy 别名可能会导致类似的问题?

    其他可能导致类似问题的别名包括 np.int_np.complex_ 等。建议查阅 NumPy 文档以获取完整的别名列表,并在编写代码时尽量使用明确的类型。

  3. Mypy 会不会在未来的版本中解决这个问题?

    Mypy 开发者们已经意识到这个问题,并且正在积极探索更好的解决方案。

  4. 除了类型检查,使用 np.float_ 还会带来其他问题吗?

    在某些情况下,使用 np.float_ 可能会导致代码在不同平台上产生不同的结果,因为它依赖于平台默认的浮点数精度。

  5. 如何了解更多关于 Mypy 和 NumPy 类型提示的信息?

    建议查阅 Mypy 和 NumPy 的官方文档,它们提供了详细的类型提示和类型检查相关信息。