diff --git a/numpy/core/src/multiarray/arraytypes.c.src b/numpy/core/src/multiarray/arraytypes.c.src index 7cd80ba9a5af..56ac83cbbcd9 100644 --- a/numpy/core/src/multiarray/arraytypes.c.src +++ b/numpy/core/src/multiarray/arraytypes.c.src @@ -804,7 +804,7 @@ VOID_getitem(void *input, void *vap) * could have special handling. */ PyObject *base = (PyObject *)ap; - while (Py_TYPE(base) == NULL) { + while (base != NULL && Py_TYPE(base) == NULL) { base = PyArray_BASE((PyArrayObject *)base); } ret = (PyArrayObject *)PyArray_NewFromDescrAndBase( @@ -812,6 +812,15 @@ VOID_getitem(void *input, void *vap) shape.len, shape.ptr, NULL, ip, PyArray_FLAGS(ap) & ~NPY_ARRAY_F_CONTIGUOUS, NULL, base); + if (base == NULL) { + /* + * Need to create a copy, or we may point to wrong data. This path + * is taken when no "valid" array is passed. This happens for + * casts. + */ + PyObject *copy = PyArray_FromArray(ret, NULL, NPY_ARRAY_ENSURECOPY); + Py_SETREF(ret, (PyArrayObject *)copy); + } npy_free_cache_dim_obj(shape); return (PyObject *)ret; } diff --git a/numpy/core/src/multiarray/dtype_transfer.c b/numpy/core/src/multiarray/dtype_transfer.c index f8458d2d7b42..c588494e7fca 100644 --- a/numpy/core/src/multiarray/dtype_transfer.c +++ b/numpy/core/src/multiarray/dtype_transfer.c @@ -258,6 +258,7 @@ any_to_object_get_loop( data->base.free = &_any_to_object_auxdata_free; data->base.clone = &_any_to_object_auxdata_clone; data->arr_fields.base = NULL; + Py_SET_TYPE(&data->arr_fields, NULL); data->arr_fields.descr = context->descriptors[0]; Py_INCREF(data->arr_fields.descr); data->arr_fields.flags = aligned ? NPY_ARRAY_ALIGNED : 0; diff --git a/numpy/core/tests/test_dtype.py b/numpy/core/tests/test_dtype.py index f95f95893e48..9b471a5bfa95 100644 --- a/numpy/core/tests/test_dtype.py +++ b/numpy/core/tests/test_dtype.py @@ -650,6 +650,29 @@ def test_aligned_empty(self): dt = np.dtype({"names": [], "formats": [], "itemsize": 0}, align=True) assert dt == np.dtype([]) + def test_subarray_base_item(self): + arr = np.ones(3, dtype=[("f", "i", 3)]) + # Extracting the field "absorbs" the subarray into a view: + assert arr["f"].base is arr + # Extract the structured item, and then check the tuple component: + item = arr.item(0) + assert type(item) is tuple and len(item) == 1 + assert item[0].base is arr + + def test_subarray_cast_copies(self): + # Older versions of NumPy did NOT copy, but they got the ownership + # wrong (not actually knowing the correct base!). Versions since 1.21 + # (I think) crashed fairly reliable. This defines the correct behavior + # as a copy. Keeping the ownership would be possible (but harder) + arr = np.ones(3, dtype=[("f", "i", 3)]) + cast = arr.astype(object) + for fields in cast: + assert type(fields) == tuple and len(fields) == 1 + subarr = fields[0] + assert subarr.base is None + assert subarr.flags.owndata + + def iter_struct_object_dtypes(): """ Iterates over a few complex dtypes and object pattern which