8000 DEP: Deprecate setting the strides attribute of a numpy array by eendebakpt · Pull Request #28925 · numpy/numpy · GitHub
[go: up one dir, main page]

Skip to content

DEP: Deprecate setting the strides attribute of a numpy array #28925

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 28 commits into from
Jun 20, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
18c02c8
DEP: Deprecate setting the strides attribute of a numpy array
eendebakpt May 8, 2025
687cf72
update
eendebakpt May 8, 2025
b32319e
add missing changes
eendebakpt May 8, 2025
0fda654
ci
eendebakpt May 8, 2025
d7cfed3
review comments part 1
eendebakpt May 9, 2025
730c9a9
Update doc/release/upcoming_changes/28925.deprecation.rst
eendebakpt May 9, 2025
5874636
Update doc/release/upcoming_changes/28925.deprecation.rst
charris May 11, 2025
2dad334
review comments
eendebakpt May 11, 2025
288690d
apply ruff
eendebakpt May 12, 2025
8502a2f
fix merge issues
eendebakpt May 15, 2025
0c4929e
clarify release notes
eendebakpt May 15, 2025
61eee97
update warning
eendebakpt May 15, 2025
141bbb7
linter
eendebakpt May 15, 2025
88950f2
fix merge conflicts again
eendebakpt May 15, 2025
347b8d3
merge conflicts
eendebakpt May 15, 2025
4bbbfbc
correct ruff version
eendebakpt May 15, 2025
60df1f7
review comment
eendebakpt May 15, 2025
36a8ca0
Merge branch 'main' into array_stride_set
eendebakpt May 18, 2025
bce4d22
ci
eendebakpt May 18, 2025
423fd5a
review comments
eendebakpt May 26, 2025
d8c22e5
Update numpy/_core/src/multiarray/getset.c
seberg May 30, 2025
6ea5c80
Update numpy/_core/src/multiarray/getset.c
eendebakpt Jun 3, 2025
73551a3
Update numpy/__init__.pyi
seberg Jun 3, 2025
01c04e7
Update numpy/__init__.pyi
eendebakpt Jun 4, 2025
bd59c8d
Merge branch 'main' into array_stride_set
eendebakpt Jun 18, 2025
cf1288a
ruff
eendebakpt Jun 19, 2025
88c1261
Merge branch 'main' into array_stride_set
eendebakpt Jun 19, 2025
08d34a2
submodule fix
eendebakpt Jun 19, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions doc/release/upcoming_changes/28925.deprecation.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
Setting the ``strides`` attribute is deprecated
-----------------------------------------------
Setting the strides attribute is now deprecated since mutating
an array is unsafe if an array is shared, especially by multiple
threads. As an alternative, you can create a new view (no copy) via:
* `np.lib.stride_tricks.strided_window_view` if applicable,
* `np.lib.stride_tricks.as_strided` for the general case,
* or the `np.ndarray` constructor (``buffer`` is the original array) for a light-weight version.

3 changes: 2 additions & 1 deletion numpy/__init__.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,7 @@ from typing import (
# library include `typing_extensions` stubs:
# https://github.com/python/typeshed/blob/main/stdlib/typing_extensions.pyi
from _typeshed import Incomplete, StrOrBytesPath, SupportsFlush, SupportsLenAndGetItem, SupportsWrite
from typing_extensions import CapsuleType, TypeVar
from typing_extensions import CapsuleType, TypeVar, deprecated

from numpy import (
char,
Expand Down Expand Up @@ -2169,6 +2169,7 @@ class ndarray(_ArrayOrScalarCommon, Generic[_ShapeT_co, _DTypeT_co]):
def shape(self, value: _ShapeLike) -> None: ...
@property
def strides(self) -> _Shape: ...
@deprecated("Setting the strides on a NumPy array has been deprecated in NumPy 2.4")
@strides.setter
def strides(self, value: _ShapeLike) -> None: ...
def byteswap(self, inplace: builtins.bool = ...) -> Self: ...
Expand Down
22 changes: 15 additions & 7 deletions numpy/_core/src/multiarray/getset.c
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ array_shape_set(PyArrayObject *self, PyObject *val, void* NPY_UNUSED(ignored))
/* Free old dimensions and strides */
npy_free_cache_dim_array(self);
((PyArrayObject_fields *)self)->nd = nd;
((PyArrayObject_fields *)self)->dimensions = _dimensions;
((PyArrayObject_fields *)self)->dimensions = _dimensions;
((PyArrayObject_fields *)self)->strides = _dimensions + nd;

if (nd) {
Expand All @@ -95,7 +95,7 @@ array_shape_set(PyArrayObject *self, PyObject *val, void* NPY_UNUSED(ignored))
}
else {
/* Free old dimensions and strides */
npy_free_cache_dim_array(self);
npy_free_cache_dim_array(self);
((PyArrayObject_fields *)self)->nd = 0;
((PyArrayObject_fields *)self)->dimensions = NULL;
((PyArrayObject_fields *)self)->strides = NULL;
Expand All @@ -116,6 +116,19 @@ array_strides_get(PyArrayObject *self, void *NPY_UNUSED(ignored))
static int
array_strides_set(PyArrayObject *self, PyObject *obj, void *NPY_UNUSED(ignored))
{
if (obj == NULL) {
PyErr_SetString(PyExc_AttributeError,
"Cannot delete array strides");
return -1;
}

/* Deprecated NumPy 2.4, 2025-05-11 */
if (DEPRECATE("Setting the strides on a NumPy array has been deprecated in NumPy 2.4.\n"
"As an alternative, you can create a new view using np.lib.stride_tricks.as_strided."
) < 0 ) {
return -1;
}

PyArray_Dims newstrides = {NULL, -1};
PyArrayObject *new;
npy_intp numbytes = 0;
Expand All @@ -124,11 +137,6 @@ array_strides_set(PyArrayObject *self, PyObject *obj, void *NPY_UNUSED(ignored))
npy_intp upper_offset = 0;
Py_buffer view;

if (obj == NULL) {
PyErr_SetString(PyExc_AttributeError,
"Cannot delete array strides");
return -1;
}
if (!PyArray_OptionalIntpConverter(obj, &newstrides) ||
newstrides.len == -1) {
PyErr_SetString(PyExc_TypeError, "invalid strides");
Expand Down
3 changes: 2 additions & 1 deletion numpy/_core/tests/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import numpy as np
import numpy._core.umath as ncu
from numpy._core._rational_tests import rational
from numpy.lib import stride_tricks
from numpy.testing import (
HAS_REFCOUNT,
assert_,
Expand Down Expand Up @@ -558,7 +559,7 @@ def check_copy_result(x, y, ccontig, fcontig, strides=False):

def test_contiguous_flags():
a = np.ones((4, 4, 1))[::2, :, :]
a.strides = a.strides[:2] + (-123,)
a = stride_tricks.as_strided(a, strides=a.strides[:2] + (-123,))
b = np.ones((2, 2, 1, 2, 2)).swapaxes(3, 4)

def check_contig(a, ccontig, fcontig):
Expand Down
7 changes: 7 additions & 0 deletions numpy/_core/tests/test_deprecations.py
Original file line number Diff line number Diff line change
Expand Up @@ -406,6 +406,13 @@ def __array_wrap__(self, arr):
self.assert_deprecated(lambda: np.negative(test2))
assert test2.called

class TestDeprecatedArrayAttributeSetting(_DeprecationTestCase):
message = "Setting the .*on a NumPy array has been deprecated.*"

def test_deprecated_strides_set(self):
x = np.eye(2)
self.assert_deprecated(setattr, args=(x, 'strides', x.strides))


class TestDeprecatedDTypeParenthesizedRepeatCount(_DeprecationTestCase):
message = "Passing in a parenthesized single number"
Expand Down
6 changes: 3 additions & 3 deletions numpy/_core/tests/test_half.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ class TestHalf:
def setup_method(self):
# An array of all possible float16 values
self.all_f16 = np.arange(0x10000, dtype=uint16)
self.all_f16.dtype = float16
self.all_f16 = self.all_f16.view(float16)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These changes are not strictly related any more, though arguably improvements regardless.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indeed not related. The idea was to add them so the diff for the other (more complex) PR for the dtype will be small.


# NaN value can cause an invalid FP exception if HW is being used
with np.errstate(invalid='ignore'):
Expand All @@ -32,7 +32,7 @@ def setup_method(self):
self.nonan_f16 = np.concatenate(
(np.arange(0xfc00, 0x7fff, -1, dtype=uint16),
np.arange(0x0000, 0x7c01, 1, dtype=uint16)))
self.nonan_f16.dtype = float16
self.nonan_f16 = self.nonan_f16.view(float16)
self.nonan_f32 = np.array(self.nonan_f16, dtype=float32)
self.nonan_f64 = np.array(self.nonan_f16, dtype=float64)

Expand Down Expand Up @@ -218,7 +218,7 @@ def test_half_values(self):
0x0001, 0x8001,
0x0000, 0x8000,
0x7c00, 0xfc00], dtype=uint16)
b.dtype = float16
b = b.view(dtype=float16)
assert_equal(a, b)

def test_half_rounding(self):
Expand Down
36 changes: 23 additions & 13 deletions numpy/_core/tests/test_multiarray.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
from numpy._core.multiarray import _get_ndarray_c_version, dot
from numpy._core.tests._locales import CommaDecimalPointLocale
from numpy.exceptions import AxisError, ComplexWarning
from numpy.lib import stride_tricks
from numpy.lib.recfunctions import repack_fields
from numpy.testing import (
BLAS_SUPPORTS_FPE,
Expand Down Expand Up @@ -382,7 +383,8 @@ def make_array(size, offset, strides):
offset=offset * x.itemsize)
except Exception as e: 8000
raise RuntimeError(e)
r.strides = strides = strides * x.itemsize
with pytest.warns(DeprecationWarning):
r.strides = strides * x.itemsize
return r

assert_equal(make_array(4, 4, -1), np.array([4, 3, 2, 1]))
Expand All @@ -392,24 +394,28 @@ def make_array(size, offset, strides):
assert_raises(RuntimeError, make_array, 8, 3, 1)
# Check that the true extent of the array is used.
# Test relies on as_strided base not exposing a buffer.
x = np.lib.stride_tricks.as_strided(np.arange(1), (10, 10), (0, 0))
x = stride_tricks.as_strided(np.arange(1), (10, 10), (0, 0))

def set_strides(arr, strides):
arr.strides = strides
with pytest.warns(DeprecationWarning):
arr.strides = strides

assert_raises(ValueError, set_strides, x, (10 * x.itemsize, x.itemsize))

# Test for offset calculations:
x = np.lib.stride_tricks.as_strided(np.arange(10, dtype=np.int8)[-1],
x = stride_tricks.as_strided(np.arange(10, dtype=np.int8)[-1],
shape=(10,), strides=(-1,))
assert_raises(ValueError, set_strides, x[::-1], -1)
a = x[::-1]
a.strides = 1
a[::2].strides = 2
with pytest.warns(DeprecationWarning):
a.strides = 1
with pytest.warns(DeprecationWarning):
a[::2].strides = 2

# test 0d
arr_0d = np.array(0)
arr_0d.strides = ()
with pytest.warns(DeprecationWarning):
arr_0d.strides = ()
assert_raises(TypeError, set_strides, arr_0d, None)

def test_fill(self):
Expand Down Expand Up @@ -3635,7 +3641,7 @@ def test_ravel(self):
a = a.reshape(2, 1, 2, 2).swapaxes(-1, -2)
strides = list(a.strides)
strides[1] = 123
a.strides = strides
a = stride_tricks.as_strided(a, strides=strides)
assert_(a.ravel(order='K').flags.owndata)
assert_equal(a.ravel('K'), np.arange(0, 15, 2))

Expand All @@ -3644,7 +3650,7 @@ def test_ravel(self):
a = a.reshape(2, 1, 2, 2).swapaxes(-1, -2)
strides = list(a.strides)
strides[1] = 123
a.strides = strides
a = stride_tricks.as_strided(a, strides=strides)
assert_(np.may_share_memory(a.ravel(order='K'), a))
assert_equal(a.ravel(order='K'), np.arange(2**3))

Expand All @@ -3657,7 +3663,7 @@ def test_ravel(self):

# 1-element tidy strides test:
a = np.array([[1]])
a.strides = (123, 432)
a = stride_tricks.as_strided(a, strides=(123, 432))
if np.ones(1).strides == (8,):
assert_(np.may_share_memory(a.ravel('K'), a))
assert_equal(a.ravel('K').strides, (a.dtype.itemsize,))
Expand Down Expand Up @@ -4546,7 +4552,8 @@ def test_datetime64_byteorder(self):
original = np.array([['2015-02-24T00:00:00.000000000']], dtype='datetime64[ns]')

original_byte_reversed = original.copy(order='K')
original_byte_reversed.dtype = original_byte_reversed.dtype.newbyteorder('S')
new_dtype = original_byte_reversed.dtype.newbyteorder('S')
original_byte_reversed = original_byte_reversed.view(dtype=new_dtype)
original_byte_reversed.byteswap(inplace=True)

new = pickle.loads(pickle.dumps(original_byte_reversed))
Expand Down Expand Up @@ -8366,10 +8373,13 @@ def test_padded_struct_array(self):
self._check_roundtrip(x3)

@pytest.mark.valgrind_error(reason="leaks buffer info cache temporarily.")
def test_relaxed_strides(self, c=np.ones((1, 10, 10), dtype='i8')): # noqa: B008
def test_relaxed_strides(self, c=stride_tricks.as_strided( # noqa: B008
np.ones((1, 10, 10), dtype='i8'), # noqa: B008
strides=(-1, 80, 8)
)
):
# Note: c defined as parameter so that it is persistent and leak
# checks will notice gh-16934 (buffer info cache leak).
c.strides = (-1, 80, 8) # strides need to be fixed at export

assert_(memoryview(c).strides == (800, 80, 8))

Expand Down
4 changes: 2 additions & 2 deletions numpy/_core/tests/test_nditer.py
Original file line number Diff line number Diff line change
Expand Up @@ -858,7 +858,7 @@ def test_iter_nbo_align_contig():

# Unaligned input
a = np.zeros((6 * 4 + 1,), dtype='i1')[1:]
a.dtype = 'f4'
a = a.view('f4')
a[:] = np.arange(6, dtype='f4')
assert_(not a.flags.aligned)
# Without 'aligned', shouldn't copy
Expand Down Expand Up @@ -1803,7 +1803,7 @@ def test_iter_buffering():
arrays.append(np.arange(10, dtype='f4'))
# Unaligned array
a = np.zeros((4 * 16 + 1,), dtype='i1')[1:]
a.dtype = 'i4'
a = a.view('i4')
a[:] = np.arange(16, dtype='i4')
arrays.append(a)
# 4-D F-order array
Expand Down
23 changes: 11 additions & 12 deletions numpy/_core/tests/test_regression.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import numpy as np
from numpy._utils import asbytes, asunicode
from numpy.exceptions import AxisError, ComplexWarning
from numpy.lib.stride_tricks import as_strided
from numpy.testing import (
HAS_REFCOUNT,
IS_64BIT,
Expand Down Expand Up @@ -208,7 +209,7 @@ def test_mem_dot(self):
# Dummy array to detect bad memory access:
_z = np.ones(10)
_dummy = np.empty((0, 10))
z = np.lib.stride_tricks.as_strided(_z, _dummy.shape, _dummy.strides)
z = as_strided(_z, _dummy.shape, _dummy.strides)
np.dot(x, np.transpose(y), out=z)
assert_equal(_z, np.ones(10))
# Do the same for the built-in dot:
Expand Down Expand Up @@ -438,19 +439,16 @@ def test_lexsort_zerolen_custom_strides(self):
xs = np.array([], dtype='i8')
assert np.lexsort((xs,)).shape[0] == 0 # Works

xs.strides = (16,)
xs = as_strided(xs, strides=(16,))
assert np.lexsort((xs,)).shape[0] == 0 # Was: MemoryError

def test_lexsort_zerolen_custom_strides_2d(self):
xs = np.array([], dtype='i8')
xt = as_strided(xs, shape=(0, 2), strides=(16, 16))
assert np.lexsort((xt,), axis=0).shape[0] == 0

xs.shape = (0, 2)
xs.strides = (16, 16)
assert np.lexsort((xs,), axis=0).shape[0] == 0

xs.shape = (2, 0)
xs.strides = (16, 16)
assert np.lexsort((xs,), axis=0).shape[0] == 2
xt = as_strided(xs, shape=(2, 0), strides=(16, 16))
assert np.lexsort((xt,), axis=0).shape[0] == 2

def test_lexsort_invalid_axis(self):
assert_raises(AxisError, np.lexsort, (np.arange(1),), axis=2)
Expand Down Expand Up @@ -644,7 +642,7 @@ def test_reshape_order(self):
def test_reshape_zero_strides(self):
# Issue #380, test reshaping of zero strided arrays
a = np.ones(1)
a = np.lib.stride_tricks.as_strided(a, shape=(5,), strides=(0,))
a = as_strided(a, shape=(5,), strides=(0,))
assert_(a.reshape(5, 1).strides[0] == 0)

def test_reshape_zero_size(self):
Expand Down Expand Up @@ -1654,7 +1652,7 @@ def test_eq_string_and_object_array(self):

def test_nonzero_byteswap(self):
a = np.array([0x80000000, 0x00000080, 0], dtype=np.uint32)
a.dtype = np.float32
a = a.view(np.float32)
assert_equal(a.nonzero()[0], [1])
a = a.byteswap()
a = a.view(a.dtype.newbyteorder())
Expand Down Expand Up @@ -1878,7 +1876,8 @@ def test_alignment_update(self):
# Check that alignment flag is updated on stride setting
a = np.arange(10)
assert_(a.flags.aligned)
a.strides = 3
with pytest.warns(DeprecationWarning):
a.strides = 3
assert_(not a.flags.aligned)

def test_ticket_1770(self):
Expand Down
2 changes: 1 addition & 1 deletion numpy/lib/_npyio_impl.py
Original file line number Diff line number Diff line change
Expand Up @@ -1730,7 +1730,7 @@ def fromregex(file, regexp, dtype, encoding=None):
# re-interpret as a single-field structured array.
newdtype = np.dtype(dtype[dtype.names[0]])
output = np.array(seq, dtype=newdtype)
output.dtype = dtype
output = output.view(dtype)
else:
output = np.array(seq, dtype=dtype)

Expand Down
Loading
0