Как np.ndarray.tobytes () работает для dtype "объект"? - PullRequest
5 голосов
/ 03 марта 2020

Я столкнулся со странным поведением np.ndarray.tobytes(), которое заставляет меня сомневаться, что оно работает детерминистически, по крайней мере для массивов dtype=object.

import numpy as np
print(np.array([1,[2]]).dtype)
# => object
print(np.array([1,[2]]).tobytes())
# => b'0h\xa3\t\x01\x00\x00\x00H{!-\x01\x00\x00\x00'
print(np.array([1,[2]]).tobytes())
# => b'0h\xa3\t\x01\x00\x00\x00\x88\x9d)-\x01\x00\x00\x00'

В примере кода список смешанных объектов python ([1, [2]]) сначала преобразуется в массив numpy, а затем преобразуется в последовательность байтов с использованием tobytes().

Почему результирующие байтовые представления различаются для повторяющихся экземпляров одних и тех же данных? В документации просто говорится, что она преобразует ndarray в необработанные python байты, но это не относится к каким-либо ограничениям. До сих пор я наблюдал это только для dtype=object. Числовые массивы c всегда дают одинаковую последовательность байтов:

np.random.seed(42); print(np.random.rand(3).tobytes())
# b'\xecQ_\x1ew\xf8\xd7?T\xd6\xbbh@l\xee?Qg\x1e\x8f~l\xe7?'
np.random.seed(42); print(np.random.rand(3).tobytes())
# b'\xecQ_\x1ew\xf8\xd7?T\xd6\xbbh@l\xee?Qg\x1e\x8f~l\xe7?'

Я пропустил элементарную вещь об архитектуре памяти python s / numpy? Я тестировал numpy версии 1.17.2 на Ma c.


Context : я столкнулся с этой проблемой при попытке вычислить га sh для произвольных структур данных. Я надеялся, что смогу положиться на базовые c возможности сериализации tobytes(), но это неверная предпосылка. Я знаю, что pickle является стандартом для сериализации в python, но поскольку мне не требуется переносимость, а мои структуры данных содержат только числа, я сначала обратился за помощью к numpy.

1 Ответ

2 голосов
/ 03 марта 2020

Массив dtype object хранит указатели на содержащиеся в нем объекты. В CPython это соответствует id. Каждый раз, когда вы создаете новый список, он будет размещен по новому адресу памяти. Однако небольшие целые числа интернированы, поэтому 1 будет ссылаться на один и тот же целочисленный объект каждый раз.

Вы можете точно увидеть, как это работает, проверив идентификаторы некоторых примеров объектов:

>>> x = np.array([1, [2]])
>>> x.tobytes()
b'\x90\x91\x04a\xfb\x7f\x00\x00\xc8[J\xaa+\x02\x00\x00'
>>> id(x[0])
140717641208208
>>> id(1)                             # Small integers are interned
140717641208208
>>> id(x[0]).to_bytes(8, 'little')    # Checks out as the first 8 bytes
b'\x90\x91\x04a\xfb\x7f\x00\x00'
>>> id(x[1]).to_bytes(8, 'little')    # Checks out as the last 8 bytes
b'\xc8[J\xaa+\x02\x00\x00'

Как видите, это вполне детерминировано c, но сериализует информацию, которая по сути бесполезна для вас. Операция для массивов цифр c такая же, как и для массивов объектов: она возвращает представление или копию базового буфера. Содержимое буфера - это то, что сбивает вас с толку.

Поскольку вы упомянули, что вы вычисляете хэши, имейте в виду, что существует причина, по которой списки python не подлежат хешу. Вы можете иметь списки, которые равны в одно время и разные в другое. Использование идентификаторов, как правило, не очень хорошая идея для эффективного га sh.

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