From ba09393c2401a7a5ca32a21dc975eaa59ea3d825 Mon Sep 17 00:00:00 2001 From: Sebastian Berg Date: Fri, 24 Jul 2020 15:16:37 -0500 Subject: [PATCH 1/2] DEP: Deprecate size-one ragged array coercion Previously, code such as: ``` np.array([0, np.array([0])], dtype=np.int64) ``` worked by discovering the array as having a shape of `(2,)` and then using `int(np.array([0]))` (which currently succeeds). This is also a ragged array, and thus deprecated here earlier on. (As a detail, in the new code the assignment should occur as an array assignment and not using the `__int__`/`__float__` Python protocols.) Two details to note: 1. This still skips the deprecation for sequences which are not array-likes. We assume that sequences will always fail the conversion to a number. 2. The conversion to a number (or string, etc.) may still fail after the DeprecationWarning is given. This is not ideal, but narrowing it down seems tedious, since the actual assignment happens in a later step. I.e. `np.array([0, np.array([None])], dtype=np.int64)` will give the warning, but then fail anyway, since the array cannot be assigned. Closes gh-16939 --- numpy/core/src/multiarray/array_coercion.c | 53 +++++++++++++++++++++- numpy/core/tests/test_array_coercion.py | 2 +- numpy/core/tests/test_deprecations.py | 13 ++++++ 3 files changed, 66 insertions(+), 2 deletions(-) diff --git a/numpy/core/src/multiarray/array_coercion.c b/numpy/core/src/multiarray/array_coercion.c index 52cf24c411bc..4cd8c2f96732 100644 --- a/numpy/core/src/multiarray/array_coercion.c +++ b/numpy/core/src/multiarray/array_coercion.c @@ -1221,7 +1221,58 @@ PyArray_DiscoverDTypeAndShape( } else if (fixed_DType->type_num != NPY_OBJECT) { /* Only object DType supports ragged cases unify error */ - if (!too_deep) { + + /* + * We used to let certain ragged arrays pass if they also + * support e.g. conversion using `float(arr)`, which currently + * works for arrays with only one element. + * Thus we catch at least most of such cases here and give a + * DeprecationWarning instead of an error. + * Note that some of these will actually error later on when + * attempting to do the actual assign. + */ + int deprecate_single_element_ragged = 0; + coercion_cache_obj *current = *coercion_cache_head; + while (current != NULL) { + if (current->sequence) { + if (current->depth == ndim) { + /* + * Assume that only array-likes will allow the deprecated + * behaviour + */ + deprecate_single_element_ragged = 0; + break; + } + /* check next converted sequence/array-like */ + current = current->next; + continue; + } + PyArrayObject *arr = (PyArrayObject *)(current->arr_or_sequence); + assert(PyArray_NDIM(arr) + current->depth >= ndim); + if (PyArray_NDIM(arr) != ndim - current->depth) { + /* This array is not compatible with the final shape */ + if (PyArray_SIZE(arr) != 1) { + deprecate_single_element_ragged = 0; + break; + } + deprecate_single_element_ragged = 1; + } + current = current->next; + } + + if (deprecate_single_element_ragged) { + /* Deprecated 2019-07-24, NumPy 1.20 */ + if (DEPRECATE( + "setting an array element with a sequence. " + "This was supported in some cases where the elements " + "are arrays with a single element. For example " + "`np.array([1, np.array([2])], dtype=int)`. " + "In the future this will raise the same ValueError as " + "`np.array([1, [2]], dtype=int)`.") < 0) { + goto fail; + } + } + else if (!too_deep) { PyObject *shape = PyArray_IntTupleFromIntp(ndim, out_shape); PyErr_Format(PyExc_ValueError, "setting an array element with a sequence. The " diff --git a/numpy/core/tests/test_array_coercion.py b/numpy/core/tests/test_array_coercion.py index 30019b253023..07a4c5c22eeb 100644 --- a/numpy/core/tests/test_array_coercion.py +++ b/numpy/core/tests/test_array_coercion.py @@ -441,7 +441,7 @@ def test_nested_arraylikes(self, arraylike): for i in range(np.MAXDIMS - 1): nested = [nested] - with pytest.raises(ValueError): + with pytest.warns(DeprecationWarning): # It will refuse to assign the array into np.array(nested, dtype="float64") diff --git a/numpy/core/tests/test_deprecations.py b/numpy/core/tests/test_deprecations.py index 431c9bb49f0d..47258ed08e21 100644 --- a/numpy/core/tests/test_deprecations.py +++ b/numpy/core/tests/test_deprecations.py @@ -693,3 +693,16 @@ def test_deprecated(self): self.assert_deprecated(np.add.outer, args=(m, arr)) self.assert_not_deprecated(np.add.outer, args=(arr, arr)) + +class TestRaggedArray(_DeprecationTestCase): + # 2020-07-24, NumPy 1.20.0 + message = "setting an array element with a sequence" + + def test_deprecated(self): + arr = np.ones((1, 1)) + # Deprecated if the array is a leave node: + self.assert_deprecated(lambda: np.array([arr, 0], dtype=np.float64)) + self.assert_deprecated(lambda: np.array([0, arr], dtype=np.float64)) + # And when it is an assignment into a lower dimensional subarray: + self.assert_deprecated(lambda: np.array([arr, [0]], dtype=np.float64)) + self.assert_deprecated(lambda: np.array([[0], arr], dtype=np.float64)) From 1e031f199652ddf219b725a1cf7d0f47b5252c68 Mon Sep 17 00:00:00 2001 From: Sebastian Berg Date: Fri, 24 Jul 2020 15:59:44 -0500 Subject: [PATCH 2/2] Update numpy/core/src/multiarray/array_coercion.c --- numpy/core/src/multiarray/array_coercion.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/numpy/core/src/multiarray/array_coercion.c b/numpy/core/src/multiarray/array_coercion.c index 4cd8c2f96732..382645ff5be2 100644 --- a/numpy/core/src/multiarray/array_coercion.c +++ b/numpy/core/src/multiarray/array_coercion.c @@ -1261,7 +1261,7 @@ PyArray_DiscoverDTypeAndShape( } if (deprecate_single_element_ragged) { - /* Deprecated 2019-07-24, NumPy 1.20 */ + /* Deprecated 2020-07-24, NumPy 1.20 */ if (DEPRECATE( "setting an array element with a sequence. " "This was supported in some cases where the elements "