From 86acce4f4efdf29e80639082d03d13ca9a2a62dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nico=20Schl=C3=B6mer?= Date: Thu, 21 Jul 2022 11:33:01 +0200 Subject: [PATCH 1/8] deprecate scalar conversion for arrays of ndim > 0 --- .../upcoming_changes/10615.deprecation.rst | 12 ++++++++ numpy/core/function_base.py | 2 +- numpy/core/src/multiarray/common.c | 29 +++++++++++++++++++ numpy/core/src/multiarray/common.h | 7 +++++ numpy/core/src/multiarray/methods.c | 4 +-- numpy/core/src/multiarray/number.c | 6 ++-- numpy/core/tests/test_deprecations.py | 12 ++++++++ numpy/core/tests/test_multiarray.py | 17 +++++++---- 8 files changed, 75 insertions(+), 14 deletions(-) create mode 100644 doc/release/upcoming_changes/10615.deprecation.rst diff --git a/doc/release/upcoming_changes/10615.deprecation.rst b/doc/release/upcoming_changes/10615.deprecation.rst new file mode 100644 index 000000000000..5a99f825cca8 --- /dev/null +++ b/doc/release/upcoming_changes/10615.deprecation.rst @@ -0,0 +1,12 @@ +Only ndim-0 arrays are treated as scalars +----------------------------------------- +NumPy used to treat all arrays of size 1 (e.g., ``np.array([3.14])``) as scalars. +In the future, this will be limited to arrays of ndim 0 (e.g., ``np.array(3.14)``). +The following expressions will report a deprecation warning: + +.. code-block:: python + a = np.array([3.14]) + float(a) # better: a[0] to get the numpy.float or a.item() + b = np.array([[3.14]]) + c = numpy.random.rand(10) + c[0] = b # better: c[0] = b[0, 0] diff --git a/numpy/core/function_base.py b/numpy/core/function_base.py index b5323fb17f57..4082e7ec7afd 100644 --- a/numpy/core/function_base.py +++ b/numpy/core/function_base.py @@ -166,7 +166,7 @@ def linspace(start, stop, num=50, endpoint=True, retstep=False, dtype=None, y += start if endpoint and num > 1: - y[-1] = stop + y[-1, ...] = stop if axis != 0: y = _nx.moveaxis(y, 0, axis) diff --git a/numpy/core/src/multiarray/common.c b/numpy/core/src/multiarray/common.c index da8d23a262aa..1fbab9e79b9e 100644 --- a/numpy/core/src/multiarray/common.c +++ b/numpy/core/src/multiarray/common.c @@ -459,3 +459,32 @@ new_array_for_sum(PyArrayObject *ap1, PyArrayObject *ap2, PyArrayObject* out, } } +NPY_NO_EXPORT int +check_is_convertible_to_scalar(PyArrayObject *v) +{ + if (PyArray_NDIM(v) == 0) { + return 0; + } + + /* Remove this if-else block when the deprecation expires */ + if (PyArray_SIZE(v) == 1) { + /* Numpy 1.24.0, 2022-07-16 */ + if (DEPRECATE( + "Conversion of an array with ndim > 0 to a scalar " + "is deprecated, and will error in future. " + "Ensure you extract a single element from your array " + "before performing this operation. " + "(Deprecated NumPy 1.24.)") < 0) { + return -1; + } + return 0; + } else { + PyErr_SetString(PyExc_TypeError, + "only length-1 arrays can be converted to Python scalars"); + return -1; + } + + PyErr_SetString(PyExc_TypeError, + "only 0-dimensional arrays can be converted to Python scalars"); + return -1; +} diff --git a/numpy/core/src/multiarray/common.h b/numpy/core/src/multiarray/common.h index a6c117745b2f..3a61ed24b2a8 100644 --- a/numpy/core/src/multiarray/common.h +++ b/numpy/core/src/multiarray/common.h @@ -319,6 +319,13 @@ PyArray_TupleFromItems(int n, PyObject *const *items, int make_null_none) return tuple; } +/* + * Returns 0 if the array has rank 0, -1 otherwise. Prints a deprecation + * warning for arrays of _size_ 1. + */ +NPY_NO_EXPORT int +check_is_convertible_to_scalar(PyArrayObject *v); + #include "ucsnarrow.h" diff --git a/numpy/core/src/multiarray/methods.c b/numpy/core/src/multiarray/methods.c index 05b71511e730..4dc9f0ee028c 100644 --- a/numpy/core/src/multiarray/methods.c +++ b/numpy/core/src/multiarray/methods.c @@ -2776,9 +2776,7 @@ array_complex(PyArrayObject *self, PyObject *NPY_UNUSED(args)) PyArray_Descr *dtype; PyObject *c; - if (PyArray_SIZE(self) != 1) { - PyErr_SetString(PyExc_TypeError, - "only length-1 arrays can be converted to Python scalars"); + if (check_is_convertible_to_scalar(self) < 0) { return NULL; } diff --git a/numpy/core/src/multiarray/number.c b/numpy/core/src/multiarray/number.c index 292ef55a630f..2913aa70fe21 100644 --- a/numpy/core/src/multiarray/number.c +++ b/numpy/core/src/multiarray/number.c @@ -848,13 +848,11 @@ array_scalar_forward(PyArrayObject *v, PyObject *(*builtin_func)(PyObject *), const char *where) { - PyObject *scalar; - if (PyArray_SIZE(v) != 1) { - PyErr_SetString(PyExc_TypeError, "only size-1 arrays can be"\ - " converted to Python scalars"); + if (check_is_convertible_to_scalar(v) < 0) { return NULL; } + PyObject *scalar; scalar = PyArray_GETITEM(v, PyArray_DATA(v)); if (scalar == NULL) { return NULL; diff --git a/numpy/core/tests/test_deprecations.py b/numpy/core/tests/test_deprecations.py index 5c13fcd4fbc7..8e9cb23ad1b5 100644 --- a/numpy/core/tests/test_deprecations.py +++ b/numpy/core/tests/test_deprecations.py @@ -1196,3 +1196,15 @@ def test_deprecated_raised(self, dtype): np.loadtxt(["10.5"], dtype=dtype) except ValueError as e: assert isinstance(e.__cause__, DeprecationWarning) + + +class TestScalarConversion(_DeprecationTestCase): + # 2022-07-19, 1.24.0 + def test_float_conversion(self): + self.assert_deprecated(float, args=(np.array([3.14]),)) + + def test_behaviour(self): + b = np.array([[3.14]]) + c = np.zeros(5) + with pytest.warns(DeprecationWarning): + c[0] = b diff --git a/numpy/core/tests/test_multiarray.py b/numpy/core/tests/test_multiarray.py index b1e719617f8c..ddd5b3835513 100644 --- a/numpy/core/tests/test_multiarray.py +++ b/numpy/core/tests/test_multiarray.py @@ -3631,7 +3631,8 @@ def test__complex__should_not_work(self): assert_raises(TypeError, complex, d) e = np.array(['1+1j'], 'U') - assert_raises(TypeError, complex, e) + with assert_warns(DeprecationWarning): + assert_raises(TypeError, complex, e) class TestCequenceMethods: def test_array_contains(self): @@ -8637,8 +8638,10 @@ def test_to_int_scalar(self): int_funcs = (int, lambda x: x.__int__()) for int_func in int_funcs: assert_equal(int_func(np.array(0)), 0) - assert_equal(int_func(np.array([1])), 1) - assert_equal(int_func(np.array([[42]])), 42) + with assert_warns(DeprecationWarning): + assert_equal(int_func(np.array([1])), 1) + with assert_warns(DeprecationWarning): + assert_equal(int_func(np.array([[42]])), 42) assert_raises(TypeError, int_func, np.array([1, 2])) # gh-9972 @@ -8653,7 +8656,8 @@ class HasTrunc: def __trunc__(self): return 3 assert_equal(3, int_func(np.array(HasTrunc()))) - assert_equal(3, int_func(np.array([HasTrunc()]))) + with assert_warns(DeprecationWarning): + assert_equal(3, int_func(np.array([HasTrunc()]))) else: pass @@ -8662,8 +8666,9 @@ def __int__(self): raise NotImplementedError assert_raises(NotImplementedError, int_func, np.array(NotConvertible())) - assert_raises(NotImplementedError, - int_func, np.array([NotConvertible()])) + with assert_warns(DeprecationWarning): + assert_raises(NotImplementedError, + int_func, np.array([NotConvertible()])) class TestWhere: From 39abdf39df186f742ec89430b114a07f97ee127c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nico=20Schl=C3=B6mer?= Date: Thu, 21 Jul 2022 11:44:29 +0200 Subject: [PATCH 2/8] deprecation test fix --- numpy/core/tests/test_multiarray.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/numpy/core/tests/test_multiarray.py b/numpy/core/tests/test_multiarray.py index ddd5b3835513..447e84447b17 100644 --- a/numpy/core/tests/test_multiarray.py +++ b/numpy/core/tests/test_multiarray.py @@ -3606,9 +3606,13 @@ def test__complex__(self): msg = 'dtype: {0}'.format(dt) ap = complex(a) assert_equal(ap, a, msg) - bp = complex(b) + + with assert_warns(DeprecationWarning): + bp = complex(b) assert_equal(bp, b, msg) - cp = complex(c) + + with assert_warns(DeprecationWarning): + cp = complex(c) assert_equal(cp, c, msg) def test__complex__should_not_work(self): From daee465953cedb088a2fd39d7879f8f493349685 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nico=20Schl=C3=B6mer?= Date: Thu, 21 Jul 2022 12:16:21 +0200 Subject: [PATCH 3/8] fix code block --- doc/release/upcoming_changes/10615.deprecation.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/doc/release/upcoming_changes/10615.deprecation.rst b/doc/release/upcoming_changes/10615.deprecation.rst index 5a99f825cca8..7fa948ea85be 100644 --- a/doc/release/upcoming_changes/10615.deprecation.rst +++ b/doc/release/upcoming_changes/10615.deprecation.rst @@ -5,8 +5,10 @@ In the future, this will be limited to arrays of ndim 0 (e.g., ``np.array(3.14)` The following expressions will report a deprecation warning: .. code-block:: python + a = np.array([3.14]) float(a) # better: a[0] to get the numpy.float or a.item() + b = np.array([[3.14]]) c = numpy.random.rand(10) c[0] = b # better: c[0] = b[0, 0] From 2c7d334eddf45600823579573567c17e05ea148e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nico=20Schl=C3=B6mer?= Date: Thu, 21 Jul 2022 11:33:01 +0200 Subject: [PATCH 4/8] deprecate scalar conversion for arrays of ndim > 0 --- .../upcoming_changes/10615.deprecation.rst | 12 ++++++++ numpy/core/function_base.py | 2 +- numpy/core/src/multiarray/common.c | 29 +++++++++++++++++++ numpy/core/src/multiarray/common.h | 7 +++++ numpy/core/src/multiarray/methods.c | 4 +-- numpy/core/src/multiarray/number.c | 6 ++-- numpy/core/tests/test_deprecations.py | 12 ++++++++ numpy/core/tests/test_multiarray.py | 17 +++++++---- 8 files changed, 75 insertions(+), 14 deletions(-) create mode 100644 doc/release/upcoming_changes/10615.deprecation.rst diff --git a/doc/release/upcoming_changes/10615.deprecation.rst b/doc/release/upcoming_changes/10615.deprecation.rst new file mode 100644 index 000000000000..5a99f825cca8 --- /dev/null +++ b/doc/release/upcoming_changes/10615.deprecation.rst @@ -0,0 +1,12 @@ +Only ndim-0 arrays are treated as scalars +----------------------------------------- +NumPy used to treat all arrays of size 1 (e.g., ``np.array([3.14])``) as scalars. +In the future, this will be limited to arrays of ndim 0 (e.g., ``np.array(3.14)``). +The following expressions will report a deprecation warning: + +.. code-block:: python + a = np.array([3.14]) + float(a) # better: a[0] to get the numpy.float or a.item() + b = np.array([[3.14]]) + c = numpy.random.rand(10) + c[0] = b # better: c[0] = b[0, 0] diff --git a/numpy/core/function_base.py b/numpy/core/function_base.py index b5323fb17f57..4082e7ec7afd 100644 --- a/numpy/core/function_base.py +++ b/numpy/core/function_base.py @@ -166,7 +166,7 @@ def linspace(start, stop, num=50, endpoint=True, retstep=False, dtype=None, y += start if endpoint and num > 1: - y[-1] = stop + y[-1, ...] = stop if axis != 0: y = _nx.moveaxis(y, 0, axis) diff --git a/numpy/core/src/multiarray/common.c b/numpy/core/src/multiarray/common.c index da8d23a262aa..1fbab9e79b9e 100644 --- a/numpy/core/src/multiarray/common.c +++ b/numpy/core/src/multiarray/common.c @@ -459,3 +459,32 @@ new_array_for_sum(PyArrayObject *ap1, PyArrayObject *ap2, PyArrayObject* out, } } +NPY_NO_EXPORT int +check_is_convertible_to_scalar(PyArrayObject *v) +{ + if (PyArray_NDIM(v) == 0) { + return 0; + } + + /* Remove this if-else block when the deprecation expires */ + if (PyArray_SIZE(v) == 1) { + /* Numpy 1.24.0, 2022-07-16 */ + if (DEPRECATE( + "Conversion of an array with ndim > 0 to a scalar " + "is deprecated, and will error in future. " + "Ensure you extract a single element from your array " + "before performing this operation. " + "(Deprecated NumPy 1.24.)") < 0) { + return -1; + } + return 0; + } else { + PyErr_SetString(PyExc_TypeError, + "only length-1 arrays can be converted to Python scalars"); + return -1; + } + + PyErr_SetString(PyExc_TypeError, + "only 0-dimensional arrays can be converted to Python scalars"); + return -1; +} diff --git a/numpy/core/src/multiarray/common.h b/numpy/core/src/multiarray/common.h index a6c117745b2f..3a61ed24b2a8 100644 --- a/numpy/core/src/multiarray/common.h +++ b/numpy/core/src/multiarray/common.h @@ -319,6 +319,13 @@ PyArray_TupleFromItems(int n, PyObject *const *items, int make_null_none) return tuple; } +/* + * Returns 0 if the array has rank 0, -1 otherwise. Prints a deprecation + * warning for arrays of _size_ 1. + */ +NPY_NO_EXPORT int +check_is_convertible_to_scalar(PyArrayObject *v); + #include "ucsnarrow.h" diff --git a/numpy/core/src/multiarray/methods.c b/numpy/core/src/multiarray/methods.c index 05b71511e730..4dc9f0ee028c 100644 --- a/numpy/core/src/multiarray/methods.c +++ b/numpy/core/src/multiarray/methods.c @@ -2776,9 +2776,7 @@ array_complex(PyArrayObject *self, PyObject *NPY_UNUSED(args)) PyArray_Descr *dtype; PyObject *c; - if (PyArray_SIZE(self) != 1) { - PyErr_SetString(PyExc_TypeError, - "only length-1 arrays can be converted to Python scalars"); + if (check_is_convertible_to_scalar(self) < 0) { return NULL; } diff --git a/numpy/core/src/multiarray/number.c b/numpy/core/src/multiarray/number.c index 292ef55a630f..2913aa70fe21 100644 --- a/numpy/core/src/multiarray/number.c +++ b/numpy/core/src/multiarray/number.c @@ -848,13 +848,11 @@ array_scalar_forward(PyArrayObject *v, PyObject *(*builtin_func)(PyObject *), const char *where) { - PyObject *scalar; - if (PyArray_SIZE(v) != 1) { - PyErr_SetString(PyExc_TypeError, "only size-1 arrays can be"\ - " converted to Python scalars"); + if (check_is_convertible_to_scalar(v) < 0) { return NULL; } + PyObject *scalar; scalar = PyArray_GETITEM(v, PyArray_DATA(v)); if (scalar == NULL) { return NULL; diff --git a/numpy/core/tests/test_deprecations.py b/numpy/core/tests/test_deprecations.py index 5c13fcd4fbc7..8e9cb23ad1b5 100644 --- a/numpy/core/tests/test_deprecations.py +++ b/numpy/core/tests/test_deprecations.py @@ -1196,3 +1196,15 @@ def test_deprecated_raised(self, dtype): np.loadtxt(["10.5"], dtype=dtype) except ValueError as e: assert isinstance(e.__cause__, DeprecationWarning) + + +class TestScalarConversion(_DeprecationTestCase): + # 2022-07-19, 1.24.0 + def test_float_conversion(self): + self.assert_deprecated(float, args=(np.array([3.14]),)) + + def test_behaviour(self): + b = np.array([[3.14]]) + c = np.zeros(5) + with pytest.warns(DeprecationWarning): + c[0] = b diff --git a/numpy/core/tests/test_multiarray.py b/numpy/core/tests/test_multiarray.py index b1e719617f8c..ddd5b3835513 100644 --- a/numpy/core/tests/test_multiarray.py +++ b/numpy/core/tests/test_multiarray.py @@ -3631,7 +3631,8 @@ def test__complex__should_not_work(self): assert_raises(TypeError, complex, d) e = np.array(['1+1j'], 'U') - assert_raises(TypeError, complex, e) + with assert_warns(DeprecationWarning): + assert_raises(TypeError, complex, e) class TestCequenceMethods: def test_array_contains(self): @@ -8637,8 +8638,10 @@ def test_to_int_scalar(self): int_funcs = (int, lambda x: x.__int__()) for int_func in int_funcs: assert_equal(int_func(np.array(0)), 0) - assert_equal(int_func(np.array([1])), 1) - assert_equal(int_func(np.array([[42]])), 42) + with assert_warns(DeprecationWarning): + assert_equal(int_func(np.array([1])), 1) + with assert_warns(DeprecationWarning): + assert_equal(int_func(np.array([[42]])), 42) assert_raises(TypeError, int_func, np.array([1, 2])) # gh-9972 @@ -8653,7 +8656,8 @@ class HasTrunc: def __trunc__(self): return 3 assert_equal(3, int_func(np.array(HasTrunc()))) - assert_equal(3, int_func(np.array([HasTrunc()]))) + with assert_warns(DeprecationWarning): + assert_equal(3, int_func(np.array([HasTrunc()]))) else: pass @@ -8662,8 +8666,9 @@ def __int__(self): raise NotImplementedError assert_raises(NotImplementedError, int_func, np.array(NotConvertible())) - assert_raises(NotImplementedError, - int_func, np.array([NotConvertible()])) + with assert_warns(DeprecationWarning): + assert_raises(NotImplementedError, + int_func, np.array([NotConvertible()])) class TestWhere: From d8d2cebb5fb3cc30979262433507a3ca557a20b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nico=20Schl=C3=B6mer?= Date: Thu, 21 Jul 2022 11:44:29 +0200 Subject: [PATCH 5/8] deprecation test fix --- numpy/core/tests/test_multiarray.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/numpy/core/tests/test_multiarray.py b/numpy/core/tests/test_multiarray.py index ddd5b3835513..447e84447b17 100644 --- a/numpy/core/tests/test_multiarray.py +++ b/numpy/core/tests/test_multiarray.py @@ -3606,9 +3606,13 @@ def test__complex__(self): msg = 'dtype: {0}'.format(dt) ap = complex(a) assert_equal(ap, a, msg) - bp = complex(b) + + with assert_warns(DeprecationWarning): + bp = complex(b) assert_equal(bp, b, msg) - cp = complex(c) + + with assert_warns(DeprecationWarning): + cp = complex(c) assert_equal(cp, c, msg) def test__complex__should_not_work(self): From 161f40dd400ce3005a9e0ad9f3978f6ef5e7d3ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nico=20Schl=C3=B6mer?= Date: Thu, 21 Jul 2022 12:16:21 +0200 Subject: [PATCH 6/8] fix code block --- doc/release/upcoming_changes/10615.deprecation.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/doc/release/upcoming_changes/10615.deprecation.rst b/doc/release/upcoming_changes/10615.deprecation.rst index 5a99f825cca8..7fa948ea85be 100644 --- a/doc/release/upcoming_changes/10615.deprecation.rst +++ b/doc/release/upcoming_changes/10615.deprecation.rst @@ -5,8 +5,10 @@ In the future, this will be limited to arrays of ndim 0 (e.g., ``np.array(3.14)` The following expressions will report a deprecation warning: .. code-block:: python + a = np.array([3.14]) float(a) # better: a[0] to get the numpy.float or a.item() + b = np.array([[3.14]]) c = numpy.random.rand(10) c[0] = b # better: c[0] = b[0, 0] From f75ff2fe4a7cafc665cac672372dfe775c3d3b04 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nico=20Schl=C3=B6mer?= Date: Mon, 2 Jan 2023 09:51:36 +0100 Subject: [PATCH 7/8] update dates --- numpy/core/src/multiarray/common.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/numpy/core/src/multiarray/common.c b/numpy/core/src/multiarray/common.c index 1fbab9e79b9e..7f80ab31d31b 100644 --- a/numpy/core/src/multiarray/common.c +++ b/numpy/core/src/multiarray/common.c @@ -468,13 +468,13 @@ check_is_convertible_to_scalar(PyArrayObject *v) /* Remove this if-else block when the deprecation expires */ if (PyArray_SIZE(v) == 1) { - /* Numpy 1.24.0, 2022-07-16 */ + /* Numpy 1.25.0, 2023-01-02 */ if (DEPRECATE( "Conversion of an array with ndim > 0 to a scalar " "is deprecated, and will error in future. " "Ensure you extract a single element from your array " "before performing this operation. " - "(Deprecated NumPy 1.24.)") < 0) { + "(Deprecated NumPy 1.25.)") < 0) { return -1; } return 0; From 2a5d6a5e8ac6f587cefe8013839e63367c44071c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nico=20Schl=C3=B6mer?= Date: Mon, 2 Jan 2023 09:56:58 +0100 Subject: [PATCH 8/8] lint fix --- numpy/core/tests/test_deprecations.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/numpy/core/tests/test_deprecations.py b/numpy/core/tests/test_deprecations.py index adc6834d1189..0303f2e22557 100644 --- a/numpy/core/tests/test_deprecations.py +++ b/numpy/core/tests/test_deprecations.py @@ -1109,4 +1109,5 @@ def test_future_scalar_attributes(name): # Unfortunately, they are currently still valid via `np.dtype()` np.dtype(name) - name in np.sctypeDict \ No newline at end of file + name in np.sctypeDict +