Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

DEP: Deprecate setting the strides and dtype of a numpy array #28901

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

Draft
wants to merge 14 commits into
base: main
Choose a base branch
Loading
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 2 additions & 1 deletion 3 numpy/__init__.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,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 deprecated, CapsuleType, TypeVar

from numpy import (
char,
Expand Down Expand Up @@ -2149,6 +2149,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.3")
@strides.setter
def strides(self, value: _ShapeLike) -> None: ...
def byteswap(self, inplace: builtins.bool = ...) -> Self: ...
Expand Down
6 changes: 5 additions & 1 deletion 6 numpy/_core/records.py
Original file line number Diff line number Diff line change
Expand Up @@ -459,7 +459,11 @@ def __setattr__(self, attr, val):

newattr = attr not in self.__dict__
try:
ret = object.__setattr__(self, attr, val)
if attr == 'dtype':
ret = self._set_dtype(val)
#ret = object.__setattr__(self, attr, val)
else:
ret = object.__setattr__(self, attr, val)
except Exception:
fielddict = ndarray.__getattribute__(self, 'dtype').fields or {}
if attr not in fielddict:
Expand Down
38 changes: 34 additions & 4 deletions 38 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 Down Expand Up @@ -124,6 +124,14 @@ array_strides_set(PyArrayObject *self, PyObject *obj, void *NPY_UNUSED(ignored))
npy_intp upper_offset = 0;
Py_buffer view;

/* DEPRECATED 2025-05-04, NumPy 2.3 */
int ret = PyErr_WarnEx(PyExc_DeprecationWarning,
Copy link
Contributor

Choose a reason for hiding this comment

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

Why not use the DEPRECATE() macro? As e.g. done at

if (DEPRECATE("Data type alias 'a' was deprecated in NumPy 2.0. "
"Use the 'S' alias instead.") < 0) {
return NULL;
}

"Setting the strides on a NumPy array has been deprecated in NumPy 2.3.\n",
1);
if (ret) {
return -1;
}

if (obj == NULL) {
PyErr_SetString(PyExc_AttributeError,
"Cannot delete array strides");
Expand Down Expand Up @@ -367,8 +375,8 @@ array_nbytes_get(PyArrayObject *self, void *NPY_UNUSED(ignored))
* (contiguous or fortran) with compatible dimensions The shape and strides
* will be adjusted in that case as well.
*/
static int
array_descr_set(PyArrayObject *self, PyObject *arg, void *NPY_UNUSED(ignored))
int
array_descr_set_internal(PyArrayObject *self, PyObject *arg)
{
PyArray_Descr *newtype = NULL;

Expand Down Expand Up @@ -514,6 +522,28 @@ array_descr_set(PyArrayObject *self, PyObject *arg, void *NPY_UNUSED(ignored))
return -1;
}


static int
array_descr_set(PyArrayObject *self, PyObject *arg, void *NPY_UNUSED(ignored))
{
// to be replaced with PyUnstable_Object_IsUniquelyReferenced https://github.com/python/cpython/pull/133144
int unique_reference = (Py_REFCNT(self) == 1);
Copy link
Contributor

Choose a reason for hiding this comment

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

I guess we probably do not care too much about a missed deprecation warning in some cases on python 3.14, but we do have

check_unique_temporary(PyObject *lhs)
, and using it could make sure we don't miss updating this...

Copy link
Member

Choose a reason for hiding this comment

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

arr isn't a temporary though, so it probably doesn't apply but the "unique" part of that function should apply.


if (!unique_reference) {
// this will not emit deprecation warnings for all cases, but for most it will
/* DEPRECATED 2025-05-04, NumPy 2.3 */
int ret = PyErr_WarnEx(PyExc_DeprecationWarning,
"Setting the dtype on a NumPy array has been deprecated in NumPy 2.3.\n"
"Instead of changing the dtype on an array x, create a new array with x.view(new_dtype)",
1);
if (ret) {
return -1;
}
}

return array_descr_set_internal(self, arg);
}

static PyObject *
array_struct_get(PyArrayObject *self, void *NPY_UNUSED(ignored))
{
Expand Down
1 change: 1 addition & 0 deletions 1 numpy/_core/src/multiarray/getset.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,6 @@
#define NUMPY_CORE_SRC_MULTIARRAY_GETSET_H_

extern NPY_NO_EXPORT PyGetSetDef array_getsetlist[];
extern NPY_NO_EXPORT int array_descr_set_internal(PyArrayObject *self, PyObject *arg);

#endif /* NUMPY_CORE_SRC_MULTIARRAY_GETSET_H_ */
15 changes: 15 additions & 0 deletions 15 numpy/_core/src/multiarray/methods.c
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
#include "ufunc_override.h"
#include "array_coercion.h"
#include "common.h"
#include "getset.h"
#include "templ_common.h" /* for npy_mul_sizes_with_overflow */
#include "ctors.h"
#include "calculation.h"
Expand Down Expand Up @@ -2580,6 +2581,15 @@ array_trace(PyArrayObject *self,

#undef _CHKTYPENUM

static PyObject* array__set_dtype(PyObject *self, PyObject *args)
{
int r = array_descr_set_internal((PyArrayObject *)self, args);

if (r) {
return NULL;
}
Py_RETURN_NONE;
}

static PyObject *
array_clip(PyArrayObject *self,
Expand Down Expand Up @@ -3067,5 +3077,10 @@ NPY_NO_EXPORT PyMethodDef array_methods[] = {
(PyCFunction)array_to_device,
METH_VARARGS | METH_KEYWORDS, NULL},

// For dtype setting deprecation
{"_set_dtype",
(PyCFunction)array__set_dtype,
METH_O, NULL},

{NULL, NULL, 0, NULL} /* sentinel */
};
4 changes: 3 additions & 1 deletion 4 numpy/_core/tests/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@

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 +560,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
17 changes: 17 additions & 0 deletions 17 numpy/_core/tests/test_deprecations.py
Original file line number Diff line number Diff line change
Expand Up @@ -409,7 +409,24 @@ 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_dtype_set(self):
x = np.eye(2)
eendebakpt marked this conversation as resolved.
Show resolved Hide resolved

def set_dtype(x):
x.dtype = int
self.assert_deprecated(lambda: set_dtype(x))
Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe simpler and clearer as self.assert_deprecated(setattr, x, "dtype", int)?


def test_deprecated_strides_set(self):
x = np.eye(2)

def set_strides(x):
eendebakpt marked this conversation as resolved.
Show resolved Hide resolved
s = x.strides
x.strides = s

self.assert_deprecated(lambda: set_strides(x))
Copy link
Contributor

Choose a reason for hiding this comment

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

As above

class TestDeprecatedDTypeParenthesizedRepeatCount(_DeprecationTestCase):
message = "Passing in a parenthesized single number"

Expand Down
7 changes: 5 additions & 2 deletions 7 numpy/_core/tests/test_dtype.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@
import types
from itertools import permutations
from typing import Any

import pickle
import warnings
import hypothesis
import pytest
from hypothesis.extra import numpy as hynp
Expand Down Expand Up @@ -1225,7 +1226,9 @@ def test_zero_stride(self):
arr = np.broadcast_to(arr, 10)
assert arr.strides == (0,)
with pytest.raises(ValueError):
arr.dtype = "i1"
with warnings.catch_warnings(): # gh-28901
Copy link
Contributor

Choose a reason for hiding this comment

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

Might as well test that the deprecation warning happens here?

with pytest.warns(DeprecationWarning, match="Setting the dtype"):

Copy link
Contributor Author

Choose a reason for hiding this comment

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

There are some cases where we sometimes get a warning, and sometimes not (depending on optimizations perhaps? or pypy? refcounting is funny stuff). For that reason I picked warnings.catch_warnings() which works for either a warning or no warning.

I will check once more whether we can use a pytest.warns here.

warnings.filterwarnings(action="ignore", category=DeprecationWarning)
arr.dtype = "i1"

class TestDTypeMakeCanonical:
def check_canonical(self, dtype, canonical):
Expand Down
6 changes: 3 additions & 3 deletions 6 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)

# 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)
Copy link
Contributor

Choose a reason for hiding this comment

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

Total nit, but either remove the spaces around = or just remove dtype=.

assert_equal(a, b)

def test_half_rounding(self):
Expand Down
48 changes: 37 additions & 11 deletions 48 numpy/_core/tests/test_multiarray.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,11 @@
from numpy._core.tests._locales import CommaDecimalPointLocale
from numpy.exceptions import AxisError, ComplexWarning
from numpy.lib.recfunctions import repack_fields
from numpy._core.multiarray import _get_ndarray_c_version, dot
from numpy.lib import stride_tricks

# Need to test an object that does not fully implement math interface
from datetime import timedelta, datetime
from numpy.testing import (
HAS_REFCOUNT,
IS_64BIT,
Expand Down Expand Up @@ -375,6 +380,7 @@ def make_array(size, offset, strides):
make_array(0, 0, 10)

def test_set_stridesattr(self):
# gh-28901: setting strides has been deprecated
x = self.one

def make_array(size, offset, strides):
Expand All @@ -383,7 +389,11 @@ def make_array(size, offset, strides):
offset=offset * x.itemsize)
except Exception as e:
raise RuntimeError(e)
r.strides = strides = strides * x.itemsize

with warnings.catch_warnings(): # gh-28901
Copy link
Contributor

Choose a reason for hiding this comment

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

As above, I think one might as well use pytest.warns (and below too)

warnings.filterwarnings(action="ignore", category=DeprecationWarning)

r.strides = strides * x.itemsize
return r

assert_equal(make_array(4, 4, -1), np.array([4, 3, 2, 1]))
Expand All @@ -396,7 +406,9 @@ def make_array(size, offset, strides):
x = np.lib.stride_tricks.as_strided(np.arange(1), (10, 10), (0, 0))

def set_strides(arr, strides):
arr.strides = strides
with warnings.catch_warnings(): # gh-28901
warnings.filterwarnings(action="ignore", category=DeprecationWarning)
arr.strides = strides

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

Expand All @@ -405,12 +417,16 @@ def set_strides(arr, strides):
shape=(10,), strides=(-1,))
assert_raises(ValueError, set_strides, x[::-1], -1)
a = x[::-1]
a.strides = 1
a[::2].strides = 2

with warnings.catch_warnings(): # gh-28901
warnings.filterwarnings(action="ignore", category=DeprecationWarning)

a.strides = 1
a[::2].strides = 2

# test 0d
arr_0d = np.array(0)
arr_0d.strides = ()
arr_0d = np.lib.stride_tricks.as_strided(arr_0d, strides = ())
Copy link
Contributor

Choose a reason for hiding this comment

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

Remove spaces around = for arguments to functions (PEP8) -- also in the other cases below.

assert_raises(TypeError, set_strides, arr_0d, None)

def test_fill(self):
Expand Down Expand Up @@ -3631,7 +3647,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 @@ -3640,7 +3656,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 @@ -3653,7 +3669,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 @@ -4542,7 +4558,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 @@ -8340,7 +8357,10 @@ def test_padded_struct_array(self):
def test_relaxed_strides(self, c=np.ones((1, 10, 10), dtype='i8')):
# 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
with warnings.catch_warnings(): # gh-28901
warnings.filterwarnings(action="ignore", category=DeprecationWarning)

c.strides = (-1, 80, 8) # strides need to be fixed at export

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

Expand Down Expand Up @@ -8762,9 +8782,15 @@ class TestArrayAttributeDeletion:
def test_multiarray_writable_attributes_deletion(self):
# ticket #2046, should not seqfault, raise AttributeError
a = np.ones(2)
attr = ['shape', 'strides', 'data', 'dtype', 'real', 'imag', 'flat']
attr = ['shape', 'data', 'real', 'imag', 'flat']
with suppress_warnings() as sup:
sup.filter(DeprecationWarning, "Assigning the 'data' attribute")
for s in attr:
assert_raises(AttributeError, delattr, a, s)
attr = ['strides', 'dtype']
with suppress_warnings() as sup:
sup.filter(DeprecationWarning, "Assigning the 'data' attribute")
sup.filter(DeprecationWarning, "Setting the ")
for s in attr:
assert_raises(AttributeError, delattr, a, s)

Expand Down
4 changes: 2 additions & 2 deletions 4 numpy/_core/tests/test_nditer.py
Original file line number Diff line number Diff line change
Expand Up @@ -860,7 +860,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 @@ -1805,7 +1805,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
2 changes: 1 addition & 1 deletion 2 numpy/_core/tests/test_numerictypes.py
Original file line number Diff line number Diff line change
Expand Up @@ -341,7 +341,7 @@ class TestReadValuesNestedMultiple(ReadValuesNested):
class TestEmptyField:
def test_assign(self):
a = np.arange(10, dtype=np.float32)
a.dtype = [("int", "<0i4"), ("float", "<2f4")]
a = a.view(dtype = [("int", "<0i4"), ("float", "<2f4")])
assert_(a['int'].shape == (5, 0))
assert_(a['float'].shape == (5, 2))

Expand Down
Loading
Loading
Morty Proxy This is a proxified and sanitized view of the page, visit original site.