将 NumPy 数组重新解释为不同的数据类型

jmd*_*_dk 8 python arrays types numpy python-3.x

假设我有一个很大的 NumPy 数组dtype int32

import numpy as np
N = 1000  # (large) number of elements
a = np.random.randint(0, 100, N, dtype=np.int32)
Run Code Online (Sandbox Code Playgroud)

但现在我希望数据是uint32. 我可以

import numpy as np
N = 1000  # (large) number of elements
a = np.random.randint(0, 100, N, dtype=np.int32)
Run Code Online (Sandbox Code Playgroud)

甚至

b = a.astype(np.uint32)
Run Code Online (Sandbox Code Playgroud)

但在这两种情况下b都是 的副本a,而我想简单地将 中的数据重新解释auint32,以免复制内存。同样,使用np.asarray()也没有帮助。

起作用的是

b = a.astype(np.uint32, copy=False)
Run Code Online (Sandbox Code Playgroud)

它只是改变了dtype而不改变数据。这是一个引人注目的例子:

import numpy as np
a = np.array([-1, 0, 1, 2], dtype=np.int32)
print(a)
a.dtype = np.uint32
print(a)  # shows "overflow", which is what I want
Run Code Online (Sandbox Code Playgroud)

dtype我的问题是关于简单地覆盖数组的解决方案:

  1. 这是合法的吗?您能指出该功能的文档记录在哪里吗?
  2. 它实际上是否使数组的数据保持不变,即没有数据重复?
  3. 如果我想要两个数组ab共享相同的数据,但将其视为不同的,dtype该怎么办?我发现以下方法可行,但我再次担心这是否真的可以做到:
    a.dtpye = np.uint32
    
    Run Code Online (Sandbox Code Playgroud) 虽然这似乎有效,但我发现data两个数组的底层似乎并不位于内存中的同一位置,这很奇怪:
    import numpy as np
    a = np.array([-1, 0, 1, 2], dtype=np.int32)
    print(a)
    a.dtype = np.uint32
    print(a)  # shows "overflow", which is what I want
    
    Run Code Online (Sandbox Code Playgroud) 实际上,上面每次运行似乎都会给出不同的结果,所以我根本不明白那里发生了什么。
  4. 这可以扩展到其他dtype,其中最极端的可能是混合 32 和 64 位浮点数:
    import numpy as np
    a = np.array([0, 1, 2, 3], dtype=np.int32)
    b = a.view(np.uint32)
    print(a)  # [0  1  2  3]
    print(b)  # [0  1  2  3]
    a[0] = -1
    print(a)  # [-1  1  2  3]
    print(b)  # [4294967295  1  2  3]
    
    Run Code Online (Sandbox Code Playgroud) 再说一次,如果所获得的行为确实是我所追求的,这是否可以被宽恕?

Jér*_*ard 10

\n
    \n
  1. 这是合法的吗?您能指出该功能的文档记录在哪里吗?
  2. \n
\n
\n

这是合法的。然而,使用np.view(等效)更好,因为它与静态分析器兼容(因此它在某种程度上更安全​​)。事实上,文档指出:

\n
\n

它\xe2\x80\x99s 可以dtype在运行时改变数组的 。[...]\n类型不允许这种突变。想要编写静态类型代码的用户应该使用该numpy.ndarray.view方法来创建具有不同dtype.

\n
\n
\n
    \n
  1. 它实际上是否使数组的数据保持不变,即没有数据重复?
  2. \n
\n
\n

是的。由于数组仍然是同一内部内存缓冲区(基本字节数组)上的视图。Numpy 只会以不同的方式重新解释它(这是直接完成每个 Numpy 计算函数的 C 代码)。

\n
\n
    \n
  1. 如果我想要两个数组ab共享相同的数据,但将其视为不同的,该怎么办dtypes?[...]
  2. \n
\n
\n

np.view可以在这种情况下使用,就像您在示例中所做的那样。然而,结果是平台相关的。事实上,Numpy 只是重新解释内存字节,理论上负数的表示可以从一台机器改变到另一台机器。希望现在所有主流现代处理器都使用二进制补码来源)。这意味着np.in32像这样的值-1将被重新解释为2**32-1 = 4294967295类型的视图np.uint32。正符号值不变。只要您意识到这一点,就可以了,并且行为是可以预测的。

\n
\n
    \n
  1. 这可以扩展到其他dtypes,其中最极端的可能是混合 32 位和 64 位浮点数。
  2. \n
\n
\n

好吧,简单来说,这真的就像玩火一样。在这种情况下,这肯定不安全,尽管它可能在您的特定机器上工作。让我们浑水摸鱼吧。

\n

首先,np.view状态文档:

\n
\n

仅从 的表面外观无法预测视图的行为a。它还取决于a内存中的存储方式。因此,如果a是 C 顺序与 Fortran 顺序、与定义为切片或转置等,视图可能会给出不同的结果。

\n
\n

问题是 Numpy 使用 C 代码重新解释指针。因此,据我所知,严格的别名规则适用。这意味着将np.float32值重新解释为会np.float64导致未定义的行为np.float32原因之一是(通常为 4)和(通常为 8)的对齐要求不同,np.float32因此np.float64从内存中读取未对齐的值可能会导致某些架构(例如 POWER)崩溃,尽管 x86-64 处理器支持这一点。另一个原因来自编译器,由于严格的别名规则,编译器可能会在您的情况下做出错误的假设,从而过度优化代码(例如值np.float32np.float64值不能在内存中重叠,因此视图的修改不应更改原始数组) 。然而,由于 Numpy 是从 CPython 调用的,并且没有从解释器内联函数调用(可能不是 Cython),所以最后一点应该不是问题(如果您使用 Numba 或任何 JIT,则可能是这种情况)。请注意,获取np.uint8a 的视图是安全的np.float32,因为它不会违反严格的别名规则(并且对齐是好的)。这对于有效序列化 Numpy 数组很有用。相反的操作并不安全(特别是由于对齐)。

\n

关于上一节的更新:对Numpy 代码的更深入分析表明,代码的某些部分(例如类型转换函数)使用 C 调用执行安全类型双关memmove,而其他一些函数(例如所有基本一元运算符二元运算符)似乎并未执行此操作。做一个适当的类型双关语吧!此外,此类功能几乎没有经过用户测试,并且棘手的极端情况可能会导致奇怪的错误(特别是如果您在同一数组的两个视图中进行读写)。因此,使用它需要您自担风险

\n