diff options
| author | Nico Schlömer <nico.schloemer@gmail.com> | 2023-04-20 18:00:59 +0200 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2023-04-20 18:00:59 +0200 |
| commit | 1acac891f99075128450aacf2a4538de3ff9d028 (patch) | |
| tree | d7de52ce7cbac0e2ae51eb68ec2ca4289a9eb77f /numpy | |
| parent | 64e692f741da5bf781870de7f6cd1aa5bb5a4070 (diff) | |
| download | numpy-1acac891f99075128450aacf2a4538de3ff9d028.tar.gz | |
DEP: deprecate scalar conversions for arrays with ndim > 0 (#10615)
This PR reflects some of the progress achieved in issue #10404 and is used to asses the impact of the changes.
With the changes in this PR, `float(numpy.array([1.0])` now gives a warning; likewise some other things:
```python
import numpy
a = numpy.random.rand(10, 1)
a[0] = numpy.array([1.0]) # okay
a[0] = numpy.array(1.0) # okay
a[0] = 1.0 # okay
b = numpy.random.rand(10)
b[0] = numpy.array([1.0]) # ValueError: setting an array element with a sequence.
b[0, ...] = numpy.array([1.0]) # okay
b[0] = numpy.array(1.0) # okay
b[0] = 1.0 # okay
```
This aligns the behavior of numpy arrays with that of lists:
```python
float([3.14])
```
```
TypeError: float() argument must be a string or a number, not 'list'
```
```python
import numpy as np
a = np.random.rand(5)
a[0] = [3.14]
```
```
ValueError: setting an array element with a sequence.
```
Fixes #10404.
Diffstat (limited to 'numpy')
| -rw-r--r-- | numpy/core/function_base.py | 2 | ||||
| -rw-r--r-- | numpy/core/src/multiarray/common.c | 29 | ||||
| -rw-r--r-- | numpy/core/src/multiarray/common.h | 7 | ||||
| -rw-r--r-- | numpy/core/src/multiarray/methods.c | 4 | ||||
| -rw-r--r-- | numpy/core/src/multiarray/number.c | 6 | ||||
| -rw-r--r-- | numpy/core/tests/test_deprecations.py | 12 | ||||
| -rw-r--r-- | numpy/core/tests/test_multiarray.py | 25 |
7 files changed, 69 insertions, 16 deletions
diff --git a/numpy/core/function_base.py b/numpy/core/function_base.py index c82b495b1..00e4e6b0e 100644 --- a/numpy/core/function_base.py +++ b/numpy/core/function_base.py @@ -168,7 +168,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 001d299c7..573d0d606 100644 --- a/numpy/core/src/multiarray/common.c +++ b/numpy/core/src/multiarray/common.c @@ -441,3 +441,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.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.25.)") < 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 127c6250d..cb9fadc4e 100644 --- a/numpy/core/src/multiarray/common.h +++ b/numpy/core/src/multiarray/common.h @@ -332,6 +332,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 93b290020..3b2e72820 100644 --- a/numpy/core/src/multiarray/methods.c +++ b/numpy/core/src/multiarray/methods.c @@ -2804,9 +2804,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 c208fb203..35e4af79c 100644 --- a/numpy/core/src/multiarray/number.c +++ b/numpy/core/src/multiarray/number.c @@ -869,13 +869,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 b92a20a12..e47a24995 100644 --- a/numpy/core/tests/test_deprecations.py +++ b/numpy/core/tests/test_deprecations.py @@ -822,6 +822,18 @@ class TestLoadtxtParseIntsViaFloat(_DeprecationTestCase): assert isinstance(e.__cause__, DeprecationWarning) +class TestScalarConversion(_DeprecationTestCase): + # 2023-01-02, 1.25.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 + + class TestPyIntConversion(_DeprecationTestCase): message = r".*stop allowing conversion of out-of-bound.*" diff --git a/numpy/core/tests/test_multiarray.py b/numpy/core/tests/test_multiarray.py index 4a064827d..984047c87 100644 --- a/numpy/core/tests/test_multiarray.py +++ b/numpy/core/tests/test_multiarray.py @@ -3644,9 +3644,13 @@ class TestMethods: 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): @@ -3669,7 +3673,8 @@ class TestMethods: 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): @@ -8756,8 +8761,10 @@ class TestConversion: 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 @@ -8772,7 +8779,8 @@ class TestConversion: 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 @@ -8781,8 +8789,9 @@ class TestConversion: 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: |
