hashability ndarray numpy

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

  • los objetos numpy definen un método __hash__ pero sin embargo no son hashable
  • una clase que deriva 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)