Как эффективно записать необработанные байты в массив данных numpy в python 3 - PullRequest
1 голос
/ 16 января 2020

При переносе старого 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.

1 Ответ

1 голос
/ 09 марта 2020

По предложению @nawsleahcimnoraa я обнаружил, что в python 3.3+ (а не в python 2.7) объект memoryview, который возвращается arr.data в моей среде python 3 , имеет cast() метод. Таким образом, я могу сделать

arr.data.cast('B')[startIdx:endIdx] = buf[:numBytes]

Это гораздо больше похоже на то, что я имел в python 2.7. Это намного более кратко и также работает немного лучше, чем метод struct.

При тестировании этих решений я заметил одну вещь: в общем, решения python 3 были медленнее, чем python 2 версии. Например, я попробовал решение struct с использованием python 2 и python 3 и обнаружил значительное увеличение времени обработки для python 3.

Я также обнаружил довольно значительные расхождения между различными python сред одной и той же версии. Например, я обнаружил, что моя системная установка python 3.6 работала лучше, чем установка виртуальной среды python 3.6, поэтому кажется, что результаты, скорее всего, будут зависеть в значительной степени от конфигурации конкретной среды.

В целом Я доволен результатами использования метода cast() объекта memoryview, возвращенного arr.data, и буду использовать его сейчас. Однако, если кто-то обнаружит что-то, что работает лучше, я все равно хотел бы услышать об этом.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...