-
-
Notifications
You must be signed in to change notification settings - Fork 11.9k
DEP: Deprecate setting the shape attribute of a numpy array #29536
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
base: main
Are you sure you want to change the base?
Conversation
seberg
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice, I much prefer this, I think! I don't care if we even consider _set_shape() semi-public. It's the users problem to know that they are mutating a fresh array if they insist, although in general, I hope there isn't really a reason to ever do so.
(__array_finalize__ is hopefully one of the very few ones.)
numpy/_core/src/multiarray/getset.c
Outdated
| PyErr_SetString(PyExc_AttributeError, | ||
| "Cannot delete array shape"); | ||
| return -1; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess we can delete the "cannot delete" path here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes. This is supposes to be called from the python side or via array_shape_set (which checks on val), so val cannot be null. I will add an assert just in case.
| Setting the shape 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 via | ||
| `np.reshape` or `array.reshape`. For example: `x = np.arange(15); x = np.reshape(x, (3, 5))`. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should point out the option to use reshape(..., copy=False) to ensure an error (which is one reason why some places used the shape attribute setting).
We could actually roll that out in the places where things were changed here, although overall, I am not really worried about accidental copies here.
I feel we should mention _set_shape() somewhere maybe. Probably just here? I.e. say that if there is no alternative and you know that you are the only owner (for example within __array_finalize__) setting the shape is still possible via _set_shape()). This is mostly used by matrices although we discourage the behavior.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added a mention to _set_shape without further explanation (to discourage usage).
numpy/_core/src/multiarray/getset.c
Outdated
| /* Deprecated NumPy 2.4, 2025-07-29 */ | ||
| if (DEPRECATE("Setting the shape on a NumPy array has been deprecated" | ||
| " in NumPy 2.4.\nAs an alternative, you can create a new" | ||
| " view using np.reshape." |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could also mention copy=False here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Specifically, maybe add " using np.reshape (with copy=False if needed)."
Co-authored-by: Sebastian Berg <[email protected]>
… deprecate_shape_v3
seberg
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
FWIW, I am happy to try this, if others agree mildly let's give it a shot. Might apply the suggestiong or do a minor thing, but I think this should be ready rally.
|
Scipy and astropy are using the shape setting. I creates issues to keep track scipy/scipy#23522, astropy/astropy#18561. |
mhvk
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this all looks good! I really have only very minor comments -- to be honest hardly worth running CI again, but maybe just...
| # switch first and second axis | ||
| output[0].shape = (1, -1) + s0[2:] | ||
| output[1].shape = (-1, 1) + s0[2:] | ||
| output[0] = output[0].reshape((1, -1) + s0[2:]) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd tend to instead generate output separately for this case, but obviously no big deal.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Still think if makes more sense to do,
if indexing == 'xy' and ndim > 1:
output = [np.asanyarray(x).reshape((1, -1) + s0[2:]
for x in xi]
else:
output = [np.asanyarray(x).reshape(s0[:i] + (-1,) + s0[i + 1:])
for i, x in enumerate(xi)]
But I can also see that you want to minimize changes...
| `np.reshape` or `np.ndarray.reshape`. For example: ``x = np.arange(15); x = np.reshape(x, (3, 5))``. | ||
| To ensure no copy is made from the data, one can use ``np.reshape(..., copy = False)``. | ||
|
|
||
| Directly setting the shape on an array is possible with the private method `np.ndarray._set_shape`. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd add ", though is discouraged" (on a new line).
numpy/_core/src/multiarray/getset.c
Outdated
| /* Deprecated NumPy 2.4, 2025-07-29 */ | ||
| if (DEPRECATE("Setting the shape on a NumPy array has been deprecated" | ||
| " in NumPy 2.4.\nAs an alternative, you can create a new" | ||
| " view using np.reshape." |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Specifically, maybe add " using np.reshape (with copy=False if needed)."
|
p.s. I just saw that #29523 is still alive. Is that still being considered, or are we just going for it here? (I'm fine with that.) |
No decision has been made on which version to use iirc, but I am fine with this one. |
mhvk
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Basically ready! My only real comment is about the changelog snippet. The rest is mostly attempts to make usage more uniform.
numpy/_core/tests/test_regression.py
Outdated
| b.shape = (10,) | ||
|
|
||
| assert_raises(AttributeError, rs) | ||
| with warnings.catch_warnings(): # gh-28901 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since python 3.11, one can just do with warnings.catch_warnings(action="ignore", category=DeprecationWarning).
Though I think just using pytest.warns(DeprecationWarning), as you do below, is cleaner, and would suggest you use that everywhere.
| # switch first and second axis | ||
| output[0].shape = (1, -1) + s0[2:] | ||
| output[1].shape = (-1, 1) + s0[2:] | ||
| output[0] = output[0].reshape((1, -1) + s0[2:]) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Still think if makes more sense to do,
if indexing == 'xy' and ndim > 1:
output = [np.asanyarray(x).reshape((1, -1) + s0[2:]
for x in xi]
else:
output = [np.asanyarray(x).reshape(s0[:i] + (-1,) + s0[i + 1:])
for i, x in enumerate(xi)]
But I can also see that you want to minimize changes...
numpy/linalg/tests/test_linalg.py
Outdated
| if np.asarray(b).ndim == 1: | ||
| expect_resids.shape = (1,) | ||
| expect_resids = expect_resids.reshape((1,)) | ||
| assert_equal(residuals.shape, expect_resids.shape) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Surely, this line should be outside the if...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes I agree, changed.
mhvk
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks all good to me, thanks!
|
I think we even checked and fixed larger downstream packages for this, right? Because in that case I think we can try to do this (can always revert if there is larger fallout). |
|
If I recall correctly astropy, h5py and scipy have been addressed and there are PRs open for yt-project. scikit-learn still has a couple of deprecated operations I think. |
|
Thanks, I am tempted to wait for 2.5 then. If we are worried about |
@seberg Is this ready to go in? |
Addresses part of #28800. Also see the discussion in #29523.