字节序#

字节序和 ndarray 的简介#

ndarray 对象提供了一个 Python 数组接口,用于访问内存中的数据。

您想用数组查看的内存的字节序,通常与您正在运行 Python 的计算机的字节序不同。

例如,我可能在一台小端序 CPU(如 Intel Pentium)的计算机上工作,但我从一台大端序计算机写入的文件中加载了一些数据。假设我从一台 Sun(大端序)计算机写入的文件中加载了 4 个字节。我知道这 4 个字节代表两个 16 位整数。在大端序机器上,一个两字节整数的存储顺序是最高有效字节 (MSB) 优先,然后是最低有效字节 (LSB)。因此,内存中的字节顺序是:

  1. 整数 1 的 MSB

  2. 整数 1 的 LSB

  3. 整数 2 的 MSB

  4. 整数 2 的 LSB

假设这两个整数实际上是 1 和 770。因为 770 = 256 * 3 + 2,所以内存中的 4 个字节将分别包含:0, 1, 3, 2。从文件中加载的字节内容将是:

>>> big_end_buffer = bytearray([0,1,3,2])
>>> big_end_buffer
bytearray(b'\x00\x01\x03\x02')

在这种情况下,我们可能想使用一个 ndarray 来访问这些整数。我们可以围绕这块内存创建一个数组,并告诉 NumPy 这里有两个整数,它们是 16 位且是大端序的:

>>> import numpy as np
>>> big_end_arr = np.ndarray(shape=(2,),dtype='>i2', buffer=big_end_buffer)
>>> big_end_arr[0]
np.int16(1)
>>> big_end_arr[1]
np.int16(770)

注意上面数组的 dtype>i2> 表示“大端序”(< 表示小端序),i2 表示“有符号 2 字节整数”。例如,如果我们的数据表示一个单一的无符号 4 字节小端序整数,dtype 字符串将是 <u4

事实上,我们为什么不试试呢?

>>> little_end_u4 = np.ndarray(shape=(1,),dtype='<u4', buffer=big_end_buffer)
>>> little_end_u4[0] == 1 * 256**1 + 3 * 256**2 + 2 * 256**3
True

回到我们的 big_end_arr - 在这种情况下,我们的底层数据是大端序的(数据字节序),并且我们将 dtype 设置为匹配(dtype 也是大端序)。然而,有时您需要交换它们。

警告

标量不包含字节序信息,因此从数组中提取标量将返回本机字节序的整数。因此:

>>> big_end_arr[0].dtype.byteorder == little_end_u4[0].dtype.byteorder
True

NumPy 故意不尝试始终保留字节序,例如,在 numpy.concatenate 中会转换为本机字节序。

改变字节序#

正如您从前面的介绍中可以想象到的,有两种方法可以影响数组的字节序与它所查看的底层内存之间的关系:

  • 更改数组 dtype 中的字节序信息,使其将底层数据解释为不同的字节序。这就是 arr.view(arr.dtype.newbyteorder()) 的作用。

  • 更改底层数据的字节序,保持 dtype 的解释不变。这就是 arr.byteswap() 所做的。

您需要更改字节序的常见情况包括:

  1. 您的数据和 dtype 字节序不匹配,并且您想更改 dtype 以匹配数据。

  2. 您的数据和 dtype 字节序不匹配,并且您想交换数据以匹配 dtype。

  3. 您的数据和 dtype 字节序匹配,但您希望交换数据,并且 dtype 反映这一点。

数据和 dtype 字节序不匹配,更改 dtype 以匹配数据#

我们创建一个字节序不匹配的数组:

>>> wrong_end_dtype_arr = np.ndarray(shape=(2,),dtype='<i2', buffer=big_end_buffer)
>>> wrong_end_dtype_arr[0]
np.int16(256)

在这种情况下,显而易见的解决方法是更改 dtype 以提供正确的字节序:

>>> fixed_end_dtype_arr = wrong_end_dtype_arr.view(np.dtype('<i2').newbyteorder())
>>> fixed_end_dtype_arr[0]
np.int16(1)

注意内存中的数组没有改变。

>>> fixed_end_dtype_arr.tobytes() == big_end_buffer
True

数据和类型字节序不匹配,更改数据以匹配 dtype#

如果您需要内存中的数据具有特定的字节序,您可能会想这样做。例如,您可能正在将内存写入需要特定字节序的文件。

>>> fixed_end_mem_arr = wrong_end_dtype_arr.byteswap()
>>> fixed_end_mem_arr[0]
np.int16(1)

现在内存中的数组已经改变了。

>>> fixed_end_mem_arr.tobytes() == big_end_buffer
False

数据和 dtype 字节序匹配,交换数据和 dtype#

您可能有一个正确指定的数组 dtype,但您需要数组在内存中具有相反的字节序,并且希望 dtype 与之匹配,以便数组的值有意义。在这种情况下,只需执行前面两种操作即可:

>>> swapped_end_arr = big_end_arr.byteswap()
>>> swapped_end_arr = swapped_end_arr.view(swapped_end_arr.dtype.newbyteorder())
>>> swapped_end_arr[0]
np.int16(1)
>>> swapped_end_arr.tobytes() == big_end_buffer
False

使用 ndarray 的 astype 方法可以更轻松地将数据转换为特定的 dtype 和字节序。

>>> swapped_end_arr = big_end_arr.astype('<i2')
>>> swapped_end_arr[0]
np.int16(1)
>>> swapped_end_arr.tobytes() == big_end_buffer
False