-
-
Notifications
You must be signed in to change notification settings - Fork 10.9k
API: Add shape
and copy
arguments to numpy.reshape
#26292
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
Conversation
We can just leave the wart in the C API and add a new internal-only C function that has a different signature. There are a lot of wrapper functions around the public C API like that in the internal C API. |
shape
argument to numpy.reshape
shape
argument to numpy.reshape
e3bf8e5
to
bdfb126
Compare
|
||
|
||
NPY_NO_EXPORT PyObject * | ||
_reshape_with_copy_arg(PyArrayObject *array, PyArray_Dims *newdims, |
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 come up with a better name.
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.
This is OK, I might have tended to use npy_reshape_with_copy_arg
although there isn't much reason for the npy_
in such cases.
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.
this is fine
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.
👍
@ngoldbaum @seberg I think it's ready for a review. |
shape
argument to numpy.reshape
shape
and copy
arguments to numpy.reshape
bdfb126
to
d295b8d
Compare
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.
Overall mostly good, see comments inline, mostly focusing on the python API changes
numpy/_core/fromnumeric.py
Outdated
@array_function_dispatch(_reshape_dispatcher) | ||
def reshape(a, newshape, order='C'): | ||
def reshape(a, /, newshape=None, shape=None, *, order='C', copy=None): |
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.
Can we make shape
come first? Can we also make newshape
a keyword-only argument?
That way you're not lying in the error message you added below when people pass newshape
positionally by saying they passed shape
.
Also that way we can issue a deprecation warning if anyone passes newshape as a keyword argument, encouraging them to migrate to the new name.
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.
Done! I changed the order and added a deprecation warning.
|
||
|
||
NPY_NO_EXPORT PyObject * | ||
_reshape_with_copy_arg(PyArrayObject *array, PyArray_Dims *newdims, |
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.
this is fine
@@ -1,6 +1,8 @@ | |||
#ifndef NUMPY_CORE_SRC_MULTIARRAY_SHAPE_H_ | |||
#define NUMPY_CORE_SRC_MULTIARRAY_SHAPE_H_ | |||
|
|||
#include "conversion_utils.h" |
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.
usually we don't put includes in headers, can you move this to the implementation files where it's needed?
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 put this header here because _reshape_with_copy_arg
declaration now contains NPY_COPYMODE
that is defined in conversion_utils.h
. Without it, it fails with:
error: unknown type name 'NPY_COPYMODE'
So I think it's needed. There are other header files with includes, such as dtypemeta.h
.
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.
(left a comment I quickly realized was dumb and deleted, sorry for the noise)
Maybe it would be cleaner to define the new function in conversion_utils.c
?
numpy/_core/fromnumeric.py
Outdated
"You cannot specify 'newshape' and 'shape' arguments " | ||
"at the same time.") | ||
if shape is not None: | ||
newshape = 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.
as alluded to above, instead of this let's make newshape
keyword -only and also issue a deprecation warning if anyone passes it. Old uses with a positional argument continue to work with the new argument name. Old uses with a keyword argument continue to work, but with a deprecation warning so they to fix their code before we finally remove newhape.
Please also mark here in a comment that the deprecation happened in numpy 2.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.
Done!
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.
Thanks, had some comments, but looks good to me overall. I think the result order is probably a bit niche to worry about.
Nathan's comments on the Python API make sense, and it is nice that you normalize the shape for _wrapfunc
.
One comment on that, though: We should be dropping the new argument if it is None
as part of the normalization (I don't think it happens later, but I may be wrong). Otherwise we break forwarding to downstream reshape
.
PyArray_OrderConverter, &order)) { | ||
if (!NpyArg_ParseKeywords(kwds, "|$O&O&", keywords, | ||
PyArray_OrderConverter, &order, | ||
PyArray_CopyConverter, ©)) { |
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.
Oh well, this is an old parsing helper that is strange and slow nowadays, we should rewrite it. But I guess it doesn't have to be 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.
The copy
argument is expected to be either None
, False
, or True
. We want to convert it to NPY_COPYMODE
enum. I think PyArray_CopyConverter
is the easiest way to do this (it also provides validation), right?
So IMO it has to be here. Or did you mean something else?
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.
Yeah, that is fine. I mean the whole NpyArg_ParseKeywords
construct is awkward and unnecessarily complicated now (probably not when it was written).
|
||
|
||
NPY_NO_EXPORT PyObject * | ||
_reshape_with_copy_arg(PyArrayObject *array, PyArray_Dims *newdims, |
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.
This is OK, I might have tended to use npy_reshape_with_copy_arg
although there isn't much reason for the npy_
in such cases.
if (copy == NPY_COPY_ALWAYS) { | ||
PyObject *newcopy = PyArray_NewCopy(array, order); | ||
if (newcopy == NULL) { | ||
return NULL; |
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.
Not sure this is desired: When a copy is made, do we prefer to return the equivalent of copy("K")
or copy("C")
(effectively?).
I can see this go both ways:
- Prefer keeping input order when possible (i.e. change it), or
- Prefer ensuring the memory order doesn't depend on whether a no-copy reshape would have been possible in a specific case. (I.e. this)
So maybe just a note...
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 I prefer the second option, to just honor order
argument in both paths - copy and no-copy, the same way. In that place I can add:
/*
* Memory order doesn't depend on a copy/no-copy context.
* 'order' argument is always honored.
*/
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.
Done!
By "the new argument" do you mean |
Ok, I addressed all comments. |
shape
and copy
arguments to numpy.reshape
shape
and copy
arguments to numpy.reshape
numpy/_core/fromnumeric.py
Outdated
Replaced by ``shape`` argument. Retained for backward | ||
compatibility. |
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.
Replaced by ``shape`` argument. Retained for backward | |
compatibility. | |
.. deprecated:: 2.1 | |
Replaced by ``shape`` argument. Retained for backward | |
compatibility. |
(please double-check that renders correctly in the html docs)
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.
d2c1bf1
to
cd6c036
Compare
I added one docs change and rebased it. It's ready from my side. |
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.
Some nitpicks, but mostly looks good. I'll let you see what to follow up on, and I guess Nathan has looked at it so may want to have a quick look too.
return _wrapfunc(a, 'reshape', newshape, order=order) | ||
if newshape is None and shape is None: | ||
raise TypeError( | ||
"reshape() missing 1 required positional argument: '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.
Interestingly, shape=None
was previously supported as "do nothing", I don't know how that would be sensible, so probably doesn't matter.
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.
Ah, I don't think shape=None
is useful really (I think some people could even expect it flattens rather than "do nothing"). I would stick to throwing an error on shape=None
.
numpy/_core/fromnumeric.py
Outdated
if newshape is not None and shape is not None: | ||
raise ValueError( | ||
"You cannot specify 'newshape' and 'shape' arguments " | ||
"at the same time.") |
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 should be a TypeError
(bad parameters usually are), not that it matters much.
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.
(might move it into the branch below, but doesn't really matter)
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.
Done!
numpy/_core/fromnumeric.py
Outdated
kwargs = {"order": order} | ||
if copy is not None: | ||
kwargs["copy"] = copy | ||
return _wrapfunc(a, 'reshape', shape, **kwargs) |
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.
TBH, I think it is easier to just do the two branches:
if copy is not None:
return _wrapfunc(a, 'reshape', shape, order=order, copy=copy)
return _wrapfunc(a, 'reshape', shape, order=order)
Also avoids the overhead of creating and unpacking the dict again.
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.
Sure, done!
Thanks @mtsokol! |
numpy/numpy#26292 included in numpy 2.1.0 breaks usage of `order` as a positional argument into `np.reshape`.
numpy/numpy#26292 included in numpy 2.1.0 breaks usage of `order` as a positional argument into `np.reshape`.
Create NumPy 1.x and 2.x compatible versions of np.where and np.reshape np.where: When only condition is provided, np.where(condition) is a shorthand for np.asarray(condition).nonzero(). NumPy 2.1.0rc0 disallows 0D input arrays in nonzero, so np.atleast_1d is used here to remain compatible with NumPy 1.x. See numpy/numpy#26268. np.reshape: NumPy 2.1.0rc1 added shape and copy arguments to numpy.reshape. Both newshape and shape keywords are supported (use shape as newshape will be deprecated). Besides, shape cannot be None now. To remain behavior with NumPy 1.x, we now use asarray to create an ndarray. See numpy/numpy#26292. PiperOrigin-RevId: 676587056
Create NumPy 1.x and 2.x compatible versions of np.where and np.reshape np.where: When only condition is provided, np.where(condition) is a shorthand for np.asarray(condition).nonzero(). NumPy 2.1.0rc0 disallows 0D input arrays in nonzero, so np.atleast_1d is used here to remain compatible with NumPy 1.x. See numpy/numpy#26268. np.reshape: NumPy 2.1.0rc1 added shape and copy arguments to numpy.reshape. Both newshape and shape keywords are supported (use shape as newshape will be deprecated). Besides, shape cannot be None now. To remain behavior with NumPy 1.x, we now use asarray to create an ndarray. See numpy/numpy#26292. PiperOrigin-RevId: 676587056
Create NumPy 1.x and 2.x compatible versions of np.where and np.reshape np.where: When only condition is provided, np.where(condition) is a shorthand for np.asarray(condition).nonzero(). NumPy 2.1.0rc0 disallows 0D input arrays in nonzero, so np.atleast_1d is used here to remain compatible with NumPy 1.x. See numpy/numpy#26268. np.reshape: NumPy 2.1.0rc1 added shape and copy arguments to numpy.reshape. Both newshape and shape keywords are supported (use shape as newshape will be deprecated). Besides, shape cannot be None now. To remain behavior with NumPy 1.x, we now use asarray to create an ndarray. See numpy/numpy#26292. PiperOrigin-RevId: 676587056
Create NumPy 1.x and 2.x compatible versions of np.where and np.reshape np.where: When only condition is provided, np.where(condition) is a shorthand for np.asarray(condition).nonzero(). NumPy 2.1.0rc0 disallows 0D input arrays in nonzero, so np.atleast_1d is used here to remain compatible with NumPy 1.x. See numpy/numpy#26268. np.reshape: NumPy 2.1.0rc1 added shape and copy arguments to numpy.reshape. Both newshape and shape keywords are supported (use shape as newshape will be deprecated). Besides, shape cannot be None now. To remain behavior with NumPy 1.x, we now use asarray to create an ndarray. See numpy/numpy#26292. PiperOrigin-RevId: 676587056
Create NumPy 1.x and 2.x compatible versions of np.where and np.reshape np.where: When only condition is provided, np.where(condition) is a shorthand for np.asarray(condition).nonzero(). NumPy 2.1.0rc0 disallows 0D input arrays in nonzero, so np.atleast_1d is used here to remain compatible with NumPy 1.x. See numpy/numpy#26268. np.reshape: NumPy 2.1.0rc1 added shape and copy arguments to numpy.reshape. Both newshape and shape keywords are supported (use shape as newshape will be deprecated). Besides, shape cannot be None now. To remain behavior with NumPy 1.x, we now use asarray to create an ndarray. See numpy/numpy#26292. PiperOrigin-RevId: 676587056
Create NumPy 1.x and 2.x compatible versions of np.where and np.reshape np.where: When only condition is provided, np.where(condition) is a shorthand for np.asarray(condition).nonzero(). NumPy 2.1.0rc0 disallows 0D input arrays in nonzero, so np.atleast_1d is used here to remain compatible with NumPy 1.x. See numpy/numpy#26268. np.reshape: NumPy 2.1.0rc1 added shape and copy arguments to numpy.reshape. Both newshape and shape keywords are supported (use shape as newshape will be deprecated). Besides, shape cannot be None now. To remain behavior with NumPy 1.x, we now use asarray to create an ndarray. See numpy/numpy#26292. PiperOrigin-RevId: 676957264
Hi!
This is the last PR with Array API support. It adds
shape
andcopy
arguments to thenumpy.reshape
, as in the Array API standard, andnumpy.ndarray.reshape
.I found that existing
newshape
keyword is used quite a lot (GitHub search) so I don't think we should deprecate the old name. Here bothnewshape
andshape
keywords are supported.P.S. There's also[EDIT] Implemented as suggested in the comment.copy
keyword in thereshape
...