Tengo algunos problemas para entender cómo se gestiona la capacidad de hash de objetos 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 "", line 1, in TypeError: unhashable type: 'numpy.ndarray' >>> hash(ndarray) Traceback (most recent call last): File "", line 1, in 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
Cómo
__hash__
pero sin embargo no son hashable numpy.ndarray
define __hash__
y es hashable? ¿Me estoy perdiendo de algo?
Estoy usando Python 2.7.1 y numpy 1.6.1
¡Gracias por cualquier ayuda!
EDITAR: objetos añadidos id
s
EDIT2: Y siguiendo el comentario deinonychusaur y tratando de averiguar si el hashing se basa en el contenido, jugué con numpy.nparray.dtype
y tengo algo que encuentro bastante extraño:
>>> [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]
Estoy desconcertado … ¿hay algún mecanismo de almacenamiento en caché (independiente del tipo) en numpy?
Obtengo los mismos resultados en Python 2.6.6 y numpy 1.3.0. De acuerdo con el glosario de Python , un objeto debe ser hashable si __hash__
está definido (y no es None
), y se define __eq__
o __cmp__
. ndarray.__eq__
y ndarray.__hash__
están definidos y devuelven algo significativo, por lo que no veo por qué el hash
debería fallar. Después de un Google rápido, encontré esta publicación en la lista de correo python.scientific.devel , que indica que los arreglos nunca han sido diseñados para ser hashable. Entonces, ¿por qué se define ndarray.__hash__
No tengo idea. Tenga en cuenta que isinstance(nparray, collections.Hashable)
devuelve True
.
EDITAR: nparray.__hash__()
cuenta que nparray.__hash__()
devuelve lo mismo que id(nparray)
, por lo que esta es solo la implementación predeterminada. Tal vez fue difícil o imposible eliminar la implementación de __hash__
en versiones anteriores de python (la técnica __hash__ = None
aparentemente se introdujo en 2.6), por lo que utilizaron algún tipo de magia C API para lograr esto de una manera que no se propagaría a las subclases, y no le ndarray.__hash__
llamar ndarray.__hash__
explícitamente?
Las cosas son diferentes en Python 3.2.2 y el número actual 2.0.0 del repository. El método __cmp__
ya no existe, por lo que la hashabilidad ahora requiere __hash__
y __eq__
(ver el glosario de Python 3 ). En esta versión de numpy, ndarray.__hash__
está definido, pero es solo None
, por lo que no se puede llamar. hash(nparray)
falla e isinstance(nparray, collections.Hashable)
devuelve False
como se esperaba. hash(vector)
también falla.
Esta no es una respuesta clara, pero aquí hay una pista a seguir para entender este comportamiento.
Me refiero aquí al código numpy de la versión 1.6.1.
De acuerdo con la implementación del objeto numpy.ndarray
(mira, numpy/core/src/multiarray/arrayobject.c
), el método hash
se establece en 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 */
Esta propiedad tp_hash
parece estar anulada en numpy/core/src/multiarray/multiarraymodule.c
. Ver DUAL_INHERIT
, DUAL_INHERIT2
y initmultiarray
función donde se modifica el atributo tp_hash
.
Ej: PyArrayDescr_Type.tp_hash = PyArray_DescrHash
Según hashdescr.c
, el hash se implementa de la siguiente manera:
* 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)