Смена порядка байтов#

Введение в порядок байтов и ndarrays#

The ndarray — это объект, предоставляющий интерфейс массива Python для данных в памяти.

Часто случается, что память, которую вы хотите просмотреть с помощью массива, имеет другой порядок байтов, чем компьютер, на котором запущен Python.

Например, я могу работать на компьютере с little-endian процессором — таким как Intel Pentium, но я загрузил некоторые данные из файла, записанного компьютером с big-endian архитектурой. Допустим, я загрузил 4 байта из файла, записанного компьютером Sun (big-endian). Я знаю, что эти 4 байта представляют два 16-битных целых числа. На big-endian машине двухбайтовое целое число хранится со старшим значащим байтом (MSB) первым, а затем младшим значащим байтом (LSB). Таким образом, байты в порядке памяти:

  1. MSB целое число 1

  2. LSB целое число 1

  3. MSB integer 2

  4. LSB integer 2

Допустим, два целых числа были 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-битные и с порядком байтов big-endian:

>>> 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. The > означает 'big-endian' (< является little-endian) и i2 означает 'знаковое 2-байтовое целое'. Например, если наши данные представляли одно беззнаковое 4-байтовое целое с прямым порядком байтов, строка dtype была бы .

На самом деле, почему бы нам не попробовать это?

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

Возвращаясь к нашему big_end_arr - в этом случае наши базовые данные big-endian (порядок байтов данных), и мы установили dtype соответствующим образом (dtype также big-endian). Однако иногда нужно поменять их местами.

Предупреждение

Скаляры не включают информацию о порядке байтов, поэтому извлечение скаляра из массива вернёт целое число в собственном порядке байтов. Следовательно:

>>> 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=', buffer=big_end_buffer)
>>> wrong_end_dtype_arr[0]
np.int16(256)

Очевидное исправление этой ситуации — изменить тип данных, чтобы он давал правильный порядок байтов:

>>> fixed_end_dtype_arr = wrong_end_dtype_arr.view(np.dtype(').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 соответствовал, чтобы значения массива имели смысл. В этом случае вы просто выполняете обе предыдущие операции:

>>> 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

Более простой способ приведения данных к определенному dtype и порядку байтов может быть достигнут с помощью метода ndarray astype:

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