При переносе старого python 2 кода в python 3 я столкнулся с некоторыми проблемами при заполнении структурированных массивов numpy из байтовых объектов.
У меня есть парсер, который определяет специфический c dtype для каждого типа структуры данных, с которой я могу столкнуться. Поскольку, как правило, данная структура данных может иметь поля переменной длины или типа переменной, они были представлены в массиве numpy как поля объекта dtype (np.object #alternatively np.dtype('O')
).
Массив Получается из байтов (или bytearray
) путем первого заполнения полей с фиксированным типом. После этого можно создать d-тип любых подмассивов (содержащихся в полях «объекта») с использованием информации из фиксированных полей, предшествующих ему.
Вот частичный пример этого процесса (касающийся только поля с фиксированным типом), работающие в python 2. Обратите внимание, что у нас есть поле с именем 'nSamples'
, которое предположительно сообщит нам длину массива, на которую указывает поле 'samples'
массива, что будет интерпретировано в виде массива numpy с формой (2,)
и dtype sampleDtype
:
fancyDtype = np.dtype([('blah', '<u4'),
('bleh', 'S5'),
('nSamples', '<u8'),
('samples', 'O')])
sampleDtype = np.dtype([('sampleId', '<u2'),
('val', '<f4')])
bytesFromFile = bytearray(
b'*\x00\x00\x00hello\x02\x00\x00\x00\x00\x00\x00\x00\xd0\xb5'
b'\x14_\xa1\x7f\x00\x00"\x00\x00\x00\x80?]\x00\x00\x00\xa0@')
arr = np.zeros((1,), dtype=fancyDtype)
numBytesFixedPortion = 17
# Start out by just reading the fixed-type portion of the array
arr.data[:numBytesFixedPortion] = bytesFromFile[:numBytesFixedPortion]
memoryview(arr.data)[:numBytesFixedPortion] = bytesFromFile[:numBytesFixedPortion]
Оба последних утверждения здесь работают в python 2.7.
что если я наберу
arr.data
, я получу <read-write buffer for 0x7f7a93bb7080, size 25, offset 0 at 0x7f7a9339cf70>
, что говорит мне, что это буфер. Очевидно, что memoryview(arr.data)
возвращает объект memoryview
.
Оба эти утверждения вызывают следующее исключение в python 3.6:
NotImplementedError: memoryview: unsupported format T{I:blah:5s:bleh:=Q:nSamples:O:samples:}
Это говорит мне, что numpy возвращает другой тип с доступом к атрибуту data
, memoryview
, а не buffer
. Это также говорит мне, что memoryviews
работал в python 2.7, но не в python 3.6 для этой цели.
Я обнаружил похожую проблему в трекере проблем numpy: https://github.com/numpy/numpy/issues/13617 Однако проблема была быстро закрыта, и разработчик numpy указал, что это ошибка в ctypes
. Поскольку ctypes
является встроенным, я как бы отказался от надежды просто обновить его, чтобы получить исправление.
Я наконец наткнулся на решение, которое работает, хотя оно занимает примерно вдвое больше, чем python 2.7 метод. Это:
import struct
struct.pack_into(
'B' * numBytesFixedPortion, # fmt
arr.data, # buffer
0, # offset
*buf[:numBytesFixedPortion] # unpacked byte values
)
Сотрудник также предложил попробовать использовать это решение:
arrView = arr.view('u1')
arrView[:numBytesFixedPortion] = buf[:numBytesFixedPortion]
Однако при этом я получаю исключение:
File "/home/tintedFrantic/anaconda2/envs/py3/lib/python3.6/site-packages/numpy/core/_internal.py", line 461, in _view_is_safe
raise TypeError("Cannot change data-type for object array.")
TypeError: Cannot change data-type for object array.
Обратите внимание, что я получаю это исключение в python 2.7 и 3.6. Похоже, numpy запрещает просмотр массивов с любыми полями object
. (Помимо: я смог заставить numpy сделать это правильно, закомментировав проверку для полей типа объекта в коде numpy, хотя это кажется опасным решением (и не очень переносимым)).
Я также пытался создать отдельные массивы, один с полями fixed-dtype и один с полем object-dtype, а затем с помощью numpy.lib.recfunctions.merge_arrays
их объединить. Это терпит неудачу с крипт c сообщением, которое я не могу вспомнить.
Я немного растерялся. Я просто хочу записать несколько произвольных байтов в основную память массива numpy и сделать это эффективно. Кажется, это не должно быть слишком сложно, но я не нашел хорошего способа сделать это. Мне бы хотелось, чтобы решение не было взломанным, поскольку оно касается систем, которые требуют высокой надежности. Если ничего лучше не существует, я воспользуюсь решением struct.pack_into()
, но я надеюсь, что кто-то там найдет лучший способ. Между прочим, НЕ использование полей object-dtype НЕ является жизнеспособным вариантом, так как затраты на это будут непомерно высокими.
Если это имеет значение, я использую numpy 1.16.2 в python 2.7 и 1.17.4 для python 3.6.