diff options
| author | Sebastian Berg <sebastian@sipsolutions.net> | 2020-11-09 18:07:23 -0600 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2020-11-09 18:07:23 -0600 |
| commit | 17cd07f11fdbc6826c2ac5ae566012473ae94278 (patch) | |
| tree | 0f43f3844cb567a3f04a7b8ac2cd0d8516ce0fee /numpy | |
| parent | e77b53a880edf16808488084c67ef090c69b3258 (diff) | |
| download | numpy-17cd07f11fdbc6826c2ac5ae566012473ae94278.tar.gz | |
BUG: Raise promotion error if a DType was provided in array coercion (gh-17706)
The new array coercion code incorrectly promoted to "object" when a promotion was impossible and
a dtype passed in. This likely only was relevant for `dtype="V"` where different void dtypes (structured
or different length) often cannot be promoted to each other.
Previously, this worked in some cases e.g. when the array contained byte strings the correct string
was found and then cast to void. Now the void dtypes are promoted, and this fails (unless the string
lengths match).
The error for promotion is now refined in this case.
Diffstat (limited to 'numpy')
| -rw-r--r-- | numpy/core/src/multiarray/array_coercion.c | 21 | ||||
| -rw-r--r-- | numpy/core/src/multiarray/dtypemeta.c | 13 | ||||
| -rw-r--r-- | numpy/core/tests/test_multiarray.py | 32 |
3 files changed, 56 insertions, 10 deletions
diff --git a/numpy/core/src/multiarray/array_coercion.c b/numpy/core/src/multiarray/array_coercion.c index ffb9bdbe8..53d891049 100644 --- a/numpy/core/src/multiarray/array_coercion.c +++ b/numpy/core/src/multiarray/array_coercion.c @@ -602,12 +602,13 @@ npy_free_coercion_cache(coercion_cache_obj *next) { * * @param out_descr The current descriptor. * @param descr The newly found descriptor to promote with + * @param fixed_DType The user provided (fixed) DType or NULL * @param flags dtype discover flags to signal failed promotion. * @return -1 on error, 0 on success. */ static NPY_INLINE int handle_promotion(PyArray_Descr **out_descr, PyArray_Descr *descr, - enum _dtype_discovery_flags *flags) + PyArray_DTypeMeta *fixed_DType, enum _dtype_discovery_flags *flags) { assert(!(*flags & DESCRIPTOR_WAS_SET)); @@ -617,7 +618,11 @@ handle_promotion(PyArray_Descr **out_descr, PyArray_Descr *descr, return 0; } PyArray_Descr *new_descr = PyArray_PromoteTypes(descr, *out_descr); - if (new_descr == NULL) { + if (NPY_UNLIKELY(new_descr == NULL)) { + if (fixed_DType != NULL) { + /* If a DType is fixed, promotion must not fail. */ + return -1; + } PyErr_Clear(); *flags |= PROMOTION_FAILED; /* Continue with object, since we may need the dimensionality */ @@ -632,13 +637,15 @@ handle_promotion(PyArray_Descr **out_descr, PyArray_Descr *descr, * Handle a leave node (known scalar) during dtype and shape discovery. * * @param obj The python object or nested sequence to convert - * @param max_dims The maximum number of dimensions. * @param curr_dims The current number of dimensions (depth in the recursion) + * @param max_dims The maximum number of dimensions. * @param out_shape The discovered output shape, will be filled - * @param coercion_cache The coercion cache object to use. - * @param DType the DType class that should be used, or NULL, if not provided. + * @param fixed_DType The user provided (fixed) DType or NULL * @param flags used signal that this is a ragged array, used internally and * can be expanded if necessary. + * @param DType the DType class that should be used, or NULL, if not provided. + * + * @return 0 on success -1 on error */ static NPY_INLINE int handle_scalar( @@ -663,7 +670,7 @@ handle_scalar( if (descr == NULL) { return -1; } - if (handle_promotion(out_descr, descr, flags) < 0) { + if (handle_promotion(out_descr, descr, fixed_DType, flags) < 0) { Py_DECREF(descr); return -1; } @@ -959,7 +966,7 @@ PyArray_DiscoverDTypeAndShape_Recursive( /* object array with no elements, no need to promote/adjust. */ return max_dims; } - if (handle_promotion(out_descr, cast_descr, flags) < 0) { + if (handle_promotion(out_descr, cast_descr, fixed_DType, flags) < 0) { Py_DECREF(cast_descr); return -1; } diff --git a/numpy/core/src/multiarray/dtypemeta.c b/numpy/core/src/multiarray/dtypemeta.c index af14bb7e5..e63a60738 100644 --- a/numpy/core/src/multiarray/dtypemeta.c +++ b/numpy/core/src/multiarray/dtypemeta.c @@ -263,8 +263,17 @@ void_common_instance(PyArray_Descr *descr1, PyArray_Descr *descr2) * are equivalent. */ if (!PyArray_CanCastTypeTo(descr1, descr2, NPY_EQUIV_CASTING)) { - PyErr_SetString(PyExc_TypeError, - "invalid type promotion with structured or void datatype(s)."); + if (descr1->subarray == NULL && descr1->names == NULL && + descr2->subarray == NULL && descr2->names == NULL) { + PyErr_SetString(PyExc_TypeError, + "Invalid type promotion with void datatypes of different " + "lengths. Use the `np.bytes_` datatype instead to pad the " + "shorter value with trailing zero bytes."); + } + else { + PyErr_SetString(PyExc_TypeError, + "invalid type promotion with structured datatype(s)."); + } return NULL; } Py_INCREF(descr1); diff --git a/numpy/core/tests/test_multiarray.py b/numpy/core/tests/test_multiarray.py index 69458bae2..515287f16 100644 --- a/numpy/core/tests/test_multiarray.py +++ b/numpy/core/tests/test_multiarray.py @@ -845,7 +845,37 @@ class TestCreation: def test_void(self): arr = np.array([], dtype='V') - assert_equal(arr.dtype.kind, 'V') + assert arr.dtype == 'V8' # current default + # Same length scalars (those that go to the same void) work: + arr = np.array([b"1234", b"1234"], dtype="V") + assert arr.dtype == "V4" + + # Promoting different lengths will fail (pre 1.20 this worked) + # by going via S5 and casting to V5. + with pytest.raises(TypeError): + np.array([b"1234", b"12345"], dtype="V") + with pytest.raises(TypeError): + np.array([b"12345", b"1234"], dtype="V") + + # Check the same for the casting path: + arr = np.array([b"1234", b"1234"], dtype="O").astype("V") + assert arr.dtype == "V4" + with pytest.raises(TypeError): + np.array([b"1234", b"12345"], dtype="O").astype("V") + + @pytest.mark.parametrize("idx", + [pytest.param(Ellipsis, id="arr"), pytest.param((), id="scalar")]) + def test_structured_void_promotion(self, idx): + arr = np.array( + [np.array(1, dtype="i,i")[idx], np.array(2, dtype='i,i')[idx]], + dtype="V") + assert_array_equal(arr, np.array([(1, 1), (2, 2)], dtype="i,i")) + # The following fails to promote the two dtypes, resulting in an error + with pytest.raises(TypeError): + np.array( + [np.array(1, dtype="i,i")[idx], np.array(2, dtype='i,i,i')[idx]], + dtype="V") + def test_too_big_error(self): # 45341 is the smallest integer greater than sqrt(2**31 - 1). |
