Numpy ndarray hashability
У меня есть некоторые проблемы с пониманием того, как управляется хешируемость объектов numpy.
>>> import numpy as np
>>> class Vector(np.ndarray):
... pass
>>> nparray = np.array([0.])
>>> vector = Vector(shape=(1,), buffer=nparray)
>>> ndarray = np.ndarray(shape=(1,), buffer=nparray)
>>> nparray
array([ 0.])
>>> ndarray
array([ 0.])
>>> vector
Vector([ 0.])
>>> '__hash__' in dir(nparray)
True
>>> '__hash__' in dir(ndarray)
True
>>> '__hash__' in dir(vector)
True
>>> hash(nparray)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unhashable type: 'numpy.ndarray'
>>> hash(ndarray)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unhashable type: 'numpy.ndarray'
>>> hash(vector)
-9223372036586049780
>>> nparray.__hash__()
269709177
>>> ndarray.__hash__()
269702147
>>> vector.__hash__()
-9223372036586049780
>>> id(nparray)
4315346832
>>> id(ndarray)
4315234352
>>> id(vector)
4299616456
>>> nparray.__hash__() == id(nparray)
False
>>> ndarray.__hash__() == id(ndarray)
False
>>> vector.__hash__() == id(vector)
False
>>> hash(vector) == vector.__hash__()
True
Как получилось
- объекты numpy определяют метод
__hash__
, но, тем не менее, не хешируются
- класс, получающий
numpy.ndarray
, определяет __hash__
и hashable?
Я что-то пропустил?
Я использую Python 2.7.1 и numpy 1.6.1
Спасибо за любую помощь!
EDIT: добавлены объекты id
s
EDIT2:
И, следуя комментарию deinonychusaur и пытаясь понять, если хеширование основано на контенте, я играл с numpy.nparray.dtype
и имел то, что я нахожу довольно странным:
>>> [Vector(shape=(1,), buffer=np.array([1], dtype=mytype), dtype=mytype) for mytype in ('float', 'int', 'float128')]
[Vector([ 1.]), Vector([1]), Vector([ 1.0], dtype=float128)]
>>> [id(Vector(shape=(1,), buffer=np.array([1], dtype=mytype), dtype=mytype)) for mytype in ('float', 'int', 'float128')]
[4317742576, 4317742576, 4317742576]
>>> [hash(Vector(shape=(1,), buffer=np.array([1], dtype=mytype), dtype=mytype)) for mytype in ('float', 'int', 'float128')]
[269858911, 269858911, 269858911]
Я озадачен... есть ли какой-нибудь (тип независимый) механизм кеширования в numpy?
Ответы
Ответ 1
Я получаю те же результаты в Python 2.6.6 и numpy 1.3.0. Согласно глоссарию Python, объект должен быть хешируемым, если __hash__
определен (и не является None
), и либо __eq__
, либо __cmp__
определены. ndarray.__eq__
и ndarray.__hash__
определены и возвращают что-то значимое, поэтому я не понимаю, почему hash
должен завершиться ошибкой. После быстрого google я нашел этот пост в списке рассылки python.scientific.devel, в котором говорится, что массивы никогда не предназначались для хэширования - так почему ndarray.__hash__
определяется, я понятия не имею. Обратите внимание, что isinstance(nparray, collections.Hashable)
возвращает True
.
EDIT: Обратите внимание, что nparray.__hash__()
возвращает то же самое, что и id(nparray)
, так что это просто реализация по умолчанию. Возможно, было трудно или невозможно удалить реализацию __hash__
в более ранних версиях python (метод __hash__ = None
, по-видимому, был введен в 2.6), поэтому они использовали какую-то магию API C для достижения этого способом, t распространяется на подклассы и не помешает вам явно вызывать ndarray.__hash__
?
В Python 3.2.2 и текущем numpy 2.0.0 из репо. Метод __cmp__
больше не существует, поэтому теперь хеширование требует __hash__
и __eq__
(см. глоссарий Python 3). В этой версии numpy определяется ndarray.__hash__
, но это просто None
, поэтому не может быть вызвано. hash(nparray)
не работает, а isinstance(nparray, collections.Hashable)
возвращает False
, как ожидалось. hash(vector)
также не работает.
Ответ 2
Это не явный ответ, но вот какой путь отслеживать, чтобы понять это поведение.
Я имею в виду здесь код numpy версии 1.6.1.
В соответствии с реализацией объекта numpy.ndarray
(смотрите, numpy/core/src/multiarray/arrayobject.c
) метод hash
установлен на NULL
.
NPY_NO_EXPORT PyTypeObject PyArray_Type = {
#if defined(NPY_PY3K)
PyVarObject_HEAD_INIT(NULL, 0)
#else
PyObject_HEAD_INIT(NULL)
0, /* ob_size */
#endif
"numpy.ndarray", /* tp_name */
sizeof(PyArrayObject), /* tp_basicsize */
&array_as_mapping, /* tp_as_mapping */
(hashfunc)0, /* tp_hash */
Это свойство tp_hash
кажется переопределенным в numpy/core/src/multiarray/multiarraymodule.c
. См. DUAL_INHERIT
, DUAL_INHERIT2
и initmultiarray
, где изменяется атрибут tp_hash
.
Пример: PyArrayDescr_Type.tp_hash = PyArray_DescrHash
Согласно hashdescr.c
, хэш реализуется следующим образом:
* How does this work ? The hash is computed from a list which contains all the
* information specific to a type. The hard work is to build the list
* (_array_descr_walk). The list is built as follows:
* * If the dtype is builtin (no fields, no subarray), then the list
* contains 6 items which uniquely define one dtype (_array_descr_builtin)
* * If the dtype is a compound array, one walk on each field. For each
* field, we append title, names, offset to the final list used for
* hashing, and then append the list recursively built for each
* corresponding dtype (_array_descr_walk_fields)
* * If the dtype is a subarray, one adds the shape tuple to the list, and
* then append the list recursively built for each corresponding type
* (_array_descr_walk_subarray)