Смена порядка байтов#
Введение в порядок байтов и ndarrays#
The ndarray — это объект, предоставляющий интерфейс массива Python для данных в памяти.
Часто случается, что память, которую вы хотите просмотреть с помощью массива, имеет другой порядок байтов, чем компьютер, на котором запущен Python.
Например, я могу работать на компьютере с little-endian процессором — таким как Intel Pentium, но я загрузил некоторые данные из файла, записанного компьютером с big-endian архитектурой. Допустим, я загрузил 4 байта из файла, записанного компьютером Sun (big-endian). Я знаю, что эти 4 байта представляют два 16-битных целых числа. На big-endian машине двухбайтовое целое число хранится со старшим значащим байтом (MSB) первым, а затем младшим значащим байтом (LSB). Таким образом, байты в порядке памяти:
MSB целое число 1
LSB целое число 1
MSB integer 2
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()делает.
Общие ситуации, в которых необходимо изменить порядок байтов:
Порядок байтов ваших данных и dtype не совпадает, и вы хотите изменить dtype, чтобы он соответствовал данным.
Порядок байтов ваших данных и dtype не совпадает, и вы хотите поменять данные местами, чтобы они соответствовали dtype
Ваши данные и порядок байтов 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