类型检查 (numpy.typing
)#
在 1.20 版本加入.
NumPy API 的大部分内容都有 PEP 484 风格的类型注解.此外,许多类型别名可供用户使用,其中最显著的是以下两个:
Mypy 插件#
在 1.21 版本加入.
一个用于管理多个平台特定注解的 mypy 插件.它的功能可以分为三个不同的部分:
分配某些
number
子类的(平台相关)精度,包括int_
、intp
和longlong
等.请参阅 标量类型 的文档,以全面了解受影响的类.没有插件的情况下,所有相关类的精度将被推断为Any
.移除所有在当前平台上不可用的扩展精度
number
子类.最值得注意的是,这包括 float128 和 complex256 等类型.没有这个插件,就 mypy 而言,*所有* 扩展精度类型将在所有平台上可用.分配
c_intp
的(平台相关)精度.没有插件时,类型将默认为ctypes.c_int64
.在 1.22 版本加入.
示例
要启用插件,必须将其添加到 mypy 的 配置文件 中.
[mypy]
plugins = numpy.typing.mypy_plugin
与运行时NumPy API的区别#
NumPy 非常灵活.试图静态地描述全部的可能性会导致类型不是很有帮助.因此,类型化的 NumPy API 通常比运行时的 NumPy API 更严格.本节描述了一些显著的差异.
ArrayLike#
ArrayLike
类型试图避免创建对象数组.例如,
>>> np.array(x**2 for x in range(10))
array(<generator object <genexpr> at ...>, dtype=object)
是有效的 NumPy 代码,将创建一个 0 维的对象数组.然而,使用 NumPy 类型时,类型检查器会对上述示例报错.如果你确实打算这样做,那么你可以使用 # type: ignore
注释:
>>> np.array(x**2 for x in range(10)) # type: ignore
或者显式地将数组类对象类型化为 Any
:
>>> from typing import Any
>>> array_like: Any = (x**2 for x in range(10))
>>> np.array(array_like)
array(<generator object <genexpr> at ...>, dtype=object)
ndarray#
可以在运行时改变数组的 dtype.例如,以下代码是有效的:
>>> x = np.array([1, 2])
>>> x.dtype = np.bool
这种类型的变异是不被允许的.希望编写静态类型代码的用户应使用 numpy.ndarray.view
方法来创建具有不同 dtype 的数组视图.
DTypeLike#
DTypeLike
类型试图避免使用如下所示的字段字典创建 dtype 对象:
>>> x = np.dtype({"field1": (float, 1), "field2": (int, 3)})
虽然这是有效的 NumPy 代码,但类型检查器会对此提出异议,因为它的使用是不被鼓励的.请参见 : 数据类型对象
数字精度#
numpy.number
子类的精度被视为一个不变的通用参数(见 NBitBase
),简化了涉及基于精度的类型转换的注释.
>>> from typing import TypeVar
>>> import numpy as np
>>> import numpy.typing as npt
>>> T = TypeVar("T", bound=npt.NBitBase)
>>> def func(a: "np.floating[T]", b: "np.floating[T]") -> "np.floating[T]":
... ...
因此,像 float16
、float32
和 float64
仍然是 floating
的子类型,但在运行时,它们不一定被认为是子类.
Timedelta64#
timedelta64
类不被认为是 signedinteger
的子类,前者仅在静态类型检查时继承自 generic
.
0D 数组#
在运行时,numpy 会积极地将任何传递的 0D 数组转换为其相应的 generic
实例.直到引入形状类型(见 PEP 646)之前,不幸的是无法区分 0D 和 >0D 数组.虽然这并不严格正确,但所有可能执行 0D 数组 -> 标量转换的操作目前都被注解为仅返回一个 ndarray
.
如果事先知道某个操作将执行 0D 数组 -> 标量 的转换,那么可以考虑使用 typing.cast
或 # type: ignore
注释手动解决问题.
记录数组 dtypes#
numpy.recarray
的 dtype,以及一般的 创建记录数组 函数,可以通过以下两种方式之一指定:
直接通过
dtype
参数.最多有五个通过
numpy.rec.format_parser
操作的辅助参数:formats
、names
、titles
、aligned
和byteorder
.
这两种方法目前被标记为互斥的,即如果指定了 dtype
,则不能指定 formats
.虽然这种互斥性在运行时没有(严格)强制执行,但结合这两种数据类型说明符可能会导致意外甚至完全错误的行为.
API#
- numpy.typing.ArrayLike = typing.Union[...]#
一个表示可以被强制转换为
ndarray
的对象的Union
.其中包括以下内容:
标量.
(嵌套)序列.
实现 __array__ 协议的对象.
在 1.20 版本加入.
另请参见
- 类似数组:
任何可以解释为 ndarray 的标量或序列.
示例
>>> import numpy as np >>> import numpy.typing as npt >>> def as_array(a: npt.ArrayLike) -> np.ndarray: ... return np.array(a)
- numpy.typing.DTypeLike = typing.Union[...]#
-
其中包括以下内容:
type
对象.字符代码或
类型
对象的名称.具有
.dtype
属性的对象.
在 1.20 版本加入.
另请参见
- 指定和构建数据类型
所有可以强制转换为数据类型的对象的综合概述.
示例
>>> import numpy as np >>> import numpy.typing as npt >>> def as_dtype(d: npt.DTypeLike) -> np.dtype: ... return np.dtype(d)
- numpy.typing.NDArray = numpy.ndarray[typing.Any, numpy.dtype[+_ScalarType_co]]#
一个
np.ndarray[Any, np.dtype[+ScalarType]]
类型别名 泛型 关于其dtype.type
.可以在运行时用于输入具有给定 dtype 和未指定形状的数组.
在 1.21 版本加入.
示例
>>> import numpy as np >>> import numpy.typing as npt >>> print(npt.NDArray) numpy.ndarray[typing.Any, numpy.dtype[+_ScalarType_co]] >>> print(npt.NDArray[np.float64]) numpy.ndarray[typing.Any, numpy.dtype[numpy.float64]] >>> NDArrayInt = npt.NDArray[np.int_] >>> a: NDArrayInt = np.arange(10) >>> def func(a: npt.ArrayLike) -> npt.NDArray[Any]: ... return np.array(a)
- class numpy.typing.NBitBase[源代码]#
一种在静态类型检查期间表示
numpy.number
精度的类型.专用于静态类型检查的目的,`NBitBase` 表示一系列分层子类的基类.每个后续子类在此用于表示较低的精度级别,例如
64Bit > 32Bit > 16Bit
.在 1.20 版本加入.
示例
下面是一个典型的使用示例:
NBitBase
在此用于注释一个函数,该函数接受一个任意精度的浮点数和整数作为参数,并返回一个新的浮点数,其精度为两者中最大的(例如np.float16 + np.int64 -> np.float64
).>>> from __future__ import annotations >>> from typing import TypeVar, TYPE_CHECKING >>> import numpy as np >>> import numpy.typing as npt >>> S = TypeVar("S", bound=npt.NBitBase) >>> T = TypeVar("T", bound=npt.NBitBase) >>> def add(a: np.floating[S], b: np.integer[T]) -> np.floating[S | T]: ... return a + b >>> a = np.float16() >>> b = np.int64() >>> out = add(a, b) >>> if TYPE_CHECKING: ... reveal_locals() ... # note: Revealed local types are: ... # note: a: numpy.floating[numpy.typing._16Bit*] ... # note: b: numpy.signedinteger[numpy.typing._64Bit*] ... # note: out: numpy.floating[numpy.typing._64Bit*]