diff --git a/doc/release/1.10.0-notes.rst b/doc/release/1.10.0-notes.rst index d5cd992031d6..a7c0e2852565 100644 --- a/doc/release/1.10.0-notes.rst +++ b/doc/release/1.10.0-notes.rst @@ -61,6 +61,9 @@ C API The changes to *swapaxes* also apply to the *PyArray_SwapAxes* C function, which now returns a view in all cases. +The dtype structure (PyArray_Descr) has a new member at the end to cache +its hash value. This shouldn't affect any well-written applications. + recarray field return types ~~~~~~~~~~~~~~~~~~~~~~~~~~~ Previously the returned types for recarray fields accessed by attribute and by diff --git a/numpy/core/include/numpy/ndarraytypes.h b/numpy/core/include/numpy/ndarraytypes.h index 78f79d5feaef..edae27c72da4 100644 --- a/numpy/core/include/numpy/ndarraytypes.h +++ b/numpy/core/include/numpy/ndarraytypes.h @@ -619,6 +619,10 @@ typedef struct _PyArray_Descr { * for NumPy 1.7.0. */ NpyAuxData *c_metadata; + /* Cached hash value (-1 if not yet computed). + * This was added for NumPy 2.0.0. + */ + npy_hash_t hash; } PyArray_Descr; typedef struct _arr_descr { diff --git a/numpy/core/include/numpy/npy_3kcompat.h b/numpy/core/include/numpy/npy_3kcompat.h index 8a9109c5c4e8..ef5b5694cb25 100644 --- a/numpy/core/include/numpy/npy_3kcompat.h +++ b/numpy/core/include/numpy/npy_3kcompat.h @@ -486,19 +486,6 @@ NpyCapsule_Check(PyObject *ptr) #endif -/* - * Hash value compatibility. - * As of Python 3.2 hash values are of type Py_hash_t. - * Previous versions use C long. - */ -#if PY_VERSION_HEX < 0x03020000 -typedef long npy_hash_t; -#define NPY_SIZEOF_HASH_T NPY_SIZEOF_LONG -#else -typedef Py_hash_t npy_hash_t; -#define NPY_SIZEOF_HASH_T NPY_SIZEOF_INTP -#endif - #ifdef __cplusplus } #endif diff --git a/numpy/core/include/numpy/npy_common.h b/numpy/core/include/numpy/npy_common.h index 92b03d20cae1..eff5dd339c50 100644 --- a/numpy/core/include/numpy/npy_common.h +++ b/numpy/core/include/numpy/npy_common.h @@ -316,6 +316,19 @@ typedef long npy_long; typedef float npy_float; typedef double npy_double; +/* + * Hash value compatibility. + * As of Python 3.2 hash values are of type Py_hash_t. + * Previous versions use C long. + */ +#if PY_VERSION_HEX < 0x03020000 +typedef long npy_hash_t; +#define NPY_SIZEOF_HASH_T NPY_SIZEOF_LONG +#else +typedef Py_hash_t npy_hash_t; +#define NPY_SIZEOF_HASH_T NPY_SIZEOF_INTP +#endif + /* * Disabling C99 complex usage: a lot of C code in numpy/scipy rely on being * able to do .real/.imag. Will have to convert code first. diff --git a/numpy/core/src/multiarray/arraytypes.c.src b/numpy/core/src/multiarray/arraytypes.c.src index 05b843fc4c92..8287c2268a11 100644 --- a/numpy/core/src/multiarray/arraytypes.c.src +++ b/numpy/core/src/multiarray/arraytypes.c.src @@ -4031,6 +4031,8 @@ static PyArray_Descr @from@_Descr = { NULL, /* c_metadata */ NULL, + /* hash */ + -1, }; /**end repeat**/ @@ -4172,6 +4174,8 @@ NPY_NO_EXPORT PyArray_Descr @from@_Descr = { NULL, /* c_metadata */ NULL, + /* hash */ + -1, }; /**end repeat**/ diff --git a/numpy/core/src/multiarray/descriptor.c b/numpy/core/src/multiarray/descriptor.c index 0993190b7235..bbcd5da36d4b 100644 --- a/numpy/core/src/multiarray/descriptor.c +++ b/numpy/core/src/multiarray/descriptor.c @@ -1591,6 +1591,7 @@ PyArray_DescrNew(PyArray_Descr *base) } Py_XINCREF(newdescr->typeobj); Py_XINCREF(newdescr->metadata); + newdescr->hash = -1; return newdescr; } @@ -1994,6 +1995,8 @@ arraydescr_names_set(PyArray_Descr *self, PyObject *val) return -1; } } + /* Invalidate cached hash value */ + self->hash = -1; /* Update dictionary keys in fields */ new_names = PySequence_Tuple(val); new_fields = PyDict_New(); @@ -2443,6 +2446,8 @@ arraydescr_setstate(PyArray_Descr *self, PyObject *args) version); return NULL; } + /* Invalidate cached hash value */ + self->hash = -1; if (version == 1 || version == 0) { if (fields != Py_None) { diff --git a/numpy/core/src/multiarray/hashdescr.c b/numpy/core/src/multiarray/hashdescr.c index 29d69fddbccd..fa5fb95cdb38 100644 --- a/numpy/core/src/multiarray/hashdescr.c +++ b/numpy/core/src/multiarray/hashdescr.c @@ -301,7 +301,6 @@ PyArray_DescrHash(PyObject* odescr) { PyArray_Descr *descr; int st; - npy_hash_t hash; if (!PyArray_DescrCheck(odescr)) { PyErr_SetString(PyExc_ValueError, @@ -310,10 +309,12 @@ PyArray_DescrHash(PyObject* odescr) } descr = (PyArray_Descr*)odescr; - st = _PyArray_DescrHashImp(descr, &hash); - if (st) { - return -1; + if (descr->hash == -1) { + st = _PyArray_DescrHashImp(descr, &descr->hash); + if (st) { + return -1; + } } - return hash; + return descr->hash; } diff --git a/numpy/core/tests/test_dtype.py b/numpy/core/tests/test_dtype.py index 3a255b038a38..85266043215e 100644 --- a/numpy/core/tests/test_dtype.py +++ b/numpy/core/tests/test_dtype.py @@ -125,6 +125,21 @@ def test_different_titles(self): 'titles': ['RRed pixel', 'Blue pixel']}) assert_dtype_not_equal(a, b) + def test_mutate(self): + # Mutating a dtype should reset the cached hash value + a = np.dtype([('yo', np.int)]) + b = np.dtype([('yo', np.int)]) + c = np.dtype([('ye', np.int)]) + assert_dtype_equal(a, b) + assert_dtype_not_equal(a, c) + a.names = ['ye'] + assert_dtype_equal(a, c) + assert_dtype_not_equal(a, b) + state = b.__reduce__()[2] + a.__setstate__(state) + assert_dtype_equal(a, b) + assert_dtype_not_equal(a, c) + def test_not_lists(self): """Test if an appropriate exception is raised when passing bad values to the dtype constructor.