什么是 `np.ndarray[Any, np.dtype[np.float64]]` 以及为什么 `np.typing.NDArray[np.float64]` 为它起别名?

Ale*_*are 5 python numpy typing

的文档称它是“np.typing.NDArray通用版本np.ndarray[Any, np.dtype[+ScalarType]]”。“通用”的概括发生在哪里?

文档中,numpy.ndarray.__class_getitem__我们有这个示例np.ndarray[Any, np.dtype[Any]],但没有解释这两个参数是什么。

为什么我可以这样做np.ndarray[float],即只使用一个参数?这意味着什么?

And*_*eak 4

在这种情况下,“通用”意味着“通用类型”(另请参见术语表),与类型相关的对象可以通过下标来生成更具体的类型“实例”(对草率的术语表示歉意,我不熟悉类型讲话)。认为typing.List这可以让您用来List[int]表示同构的整数列表。

从 Python 3.9 开始,大多数标准库集合已升级为与泛型类型本身的类型兼容。由于tuple[foo]在 3.9 之前都是无效的,因此可以安全地允许表示与过去tuple[int, int]相同的含义:两个整数的元组。typing.Tuple[int, int]

因此,从 3.9 开始,NumPy 还允许使用np.ndarray类型作为泛型,这就是np.ndarray[Any, np.dtype[Any]]所做的。这个“签名”与实际签名np.ndarray.__init__()匹配(__new__()如果我们想要正确的话):

class numpy.ndarray(shape, dtype=float, ...)
Run Code Online (Sandbox Code Playgroud)

那么,np.ndarray[foo, bar]要做的就是创建一个类型提示,这意味着“形状类型foo和 dtype的 NumPy 数组bar”。人们通常不会np.ndarray()直接调用(而是使用诸如np.array()np.full_like()之类的帮助器),因此这在 NumPy 中更加良好。

现在,由于大多数代码都使用多个可能维数的数组运行,因此必须为形状元组​​(作为np.ndarray泛型类型的第一个“参数”)指定任意数量的长度将是一件痛苦的事情。我认为这是定义类型别名的动机,该类型别名在第二个“参数”中仍然是泛型。这是np.typing.NDArray

它可以让您轻松地键入提示作为给定类型的数组,而无需说明任何有关形状的信息,涵盖了用例的大量子集(否则将使用np.ndarray[typing.Any, ...])。这仍然是一个泛型,因为您可以使用数据类型对其进行参数化。引用文档

>>> print(npt.NDArray)
numpy.ndarray[typing.Any, numpy.dtype[+ScalarType]]

>>> print(npt.NDArray[np.float64])
numpy.ndarray[typing.Any, numpy.dtype[numpy.float64]]
Run Code Online (Sandbox Code Playgroud)

与通常的泛型一样,您可以为泛型类型指定参数,但不是必需的。ScalarType派生自np.generic,一个涵盖大多数(也许是全部)NumPy 标量类型的基类。定义的库代码NDArray这里,并且对于调用_GenericAlias旧版 Python 的帮助程序( 的向后移植typing.GenericAlias)来说是相当透明的。最后得到的是一个类型别名,它在一个变量中仍然是通用的。


解决你问题的最后一部分:

为什么我可以这样做np.ndarray[float],即只使用一个参数?这意味着什么?

我认为虎头蛇尾的解释是我们需要再次查看 的签名np.ndarray()

class numpy.ndarray(shape, dtype=float, buffer=None, offset=0, strides=None, order=None)
Run Code Online (Sandbox Code Playgroud)

有一个强制参数 ( shape),所有其他参数都是可选的。所以我相信np.ndarray[float]指定它对应于形状为类型的数组float(即废话)。有一个显式检查仅允许泛型类型中存在 1 个或 2 个参数

class numpy.ndarray(shape, dtype=float, ...)
Run Code Online (Sandbox Code Playgroud)

此代码片段检查两个参数是否已传递给__class_getitem__,否则引发,并且在有效情况下遵循 的C API 版本typing.GenericAlias

我很确定没有技术原因将构造函数的其他参数ndarray从泛型类型中排除,但是有一个语义原因,即第三个参数buffer没有意义包含在类型中(或者只是一般性地推动将泛型类型的通用性降低到最常见的用例)。

话虽这么说,我还无法构建一个小示例,其中为shape泛型类型传递的类型会导致mypy. 从几次尝试来看,似乎总是检查形状,而不是typing.Any作为 的第一个参数传递的任何内容np.ndarray[...]。例如,考虑以下示例:

>>> print(npt.NDArray)
numpy.ndarray[typing.Any, numpy.dtype[+ScalarType]]

>>> print(npt.NDArray[np.float64])
numpy.ndarray[typing.Any, numpy.dtype[numpy.float64]]
Run Code Online (Sandbox Code Playgroud)

在 Python 3.9 上运行 mypy 0.991 给出

foo.py:5: error: Incompatible types in assignment (expression has type "ndarray[Any, dtype[floating[Any]]]", variable has type "ndarray[Tuple[int], dtype[signedinteger[_64Bit]]]")  [assignment]
Found 1 error in 1 file (checked 1 source file)
Run Code Online (Sandbox Code Playgroud)

仅发现数据类型不匹配,但未发现形状不匹配。如果我使用np.ndarray((3,), dtype=...)而不是np.arange(),我会看到同样的事情,所以这不仅仅是由于np.arange()助手的奇怪类型(尽管我用它作为示例,因为这是一个保证返回一维数组的函数)。由于我无法解释这种行为,所以我不能确定我的理解是否正确,但我没有更好的模型。

回到您在评论中提出的问题:

是的,那么我理解 np.ndarray[int] 就像 np.ndarray[Any, int] 是正确的吗?

不,至少我们可以排除这一点(我们在这里看到的与第一个参数一致,仅影响形状,无论它影响它的程度如何):

class numpy.ndarray(shape, dtype=float, buffer=None, offset=0, strides=None, order=None)
Run Code Online (Sandbox Code Playgroud)

结果来自mypy

foo.py:7: error: "ndarray" expects 2 type arguments, but 1 given  [type-arg]
foo.py:9: error: Incompatible types in assignment (expression has type "ndarray[Any, dtype[floating[Any]]]", variable has type "ndarray[Any, dtype[signedinteger[Any]]]")  [assignment]
foo.py:11: error: "ndarray" expects 2 type arguments, but 1 given  [type-arg]
Found 3 errors in 1 file (checked 1 source file)
Run Code Online (Sandbox Code Playgroud)

四种情况:

  1. first:显式输入为“任何形状的 int 数组”,类型正确赋值没有错误
  2. second:输入为“具有 int 类型形状和任何 dtype 的数组”,应该会失败,因为这是无意义的,但事实并非如此(请参阅前面关于形状类型检查的无能性的思考)
  3. third:显式键入另一个“任意形状的 int 数组”,被分配一个双精度数组,导致错误
  4. fourth:输入为“具有 int 类型形状和任意 dtype 的数组”,不会导致错误(请参阅second)。由于第三种情况会导致错误,而第四种情况不会,因此它们不能是彼此的别名。

还值得注意的是 mypy 抱怨存在的两行np.ndarray[np.dtype[np.int_]]

foo.py:7: 错误:“ndarray”需要 2 个类型参数,但给定 1 个 [type-arg]

就 mypy 而言,这听起来像是禁止使用泛型的单参数。我不确定为什么会出现这种情况,但这肯定会简化情况。