API: Add shape and copy arguments to numpy.reshape#26292
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.reshapeshape 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.
I guess we can come up with a better name.
There was a problem hiding this comment.
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.
|
@ngoldbaum @seberg I think it's ready for a review. |
shape argument to numpy.reshapeshape and copy arguments to numpy.reshape
bdfb126 to
d295b8d
Compare
ngoldbaum
left a comment
There was a problem hiding this comment.
Overall mostly good, see comments inline, mostly focusing on the python API changes
| # not deprecated --- copy if necessary, view otherwise | ||
| @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.
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.
Done! I changed the order and added a deprecation warning.
|
|
||
|
|
||
| NPY_NO_EXPORT PyObject * | ||
| _reshape_with_copy_arg(PyArrayObject *array, PyArray_Dims *newdims, |
| #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.
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.
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.
(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?
| "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.
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.
seberg
left a comment
There was a problem hiding this comment.
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.
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.
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.
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.
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.
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.
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.
*/
By "the new argument" do you mean |
|
Ok, I addressed all comments. |
shape and copy arguments to numpy.reshapeshape and copy arguments to numpy.reshape
| Replaced by ``shape`` argument. Retained for backward | ||
| compatibility. |
There was a problem hiding this comment.
| 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)
d2c1bf1 to
cd6c036
Compare
|
I added one docs change and rebased it. It's ready from my side. |
seberg
left a comment
There was a problem hiding this comment.
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.
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.
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.
| 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.
I think this should be a TypeError (bad parameters usually are), not that it matters much.
There was a problem hiding this comment.
(might move it into the branch below, but doesn't really matter)
| kwargs = {"order": order} | ||
| if copy is not None: | ||
| kwargs["copy"] = copy | ||
| return _wrapfunc(a, 'reshape', shape, **kwargs) |
There was a problem hiding this comment.
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.
|
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
shapeandcopyarguments to thenumpy.reshape, as in the Array API standard, andnumpy.ndarray.reshape.I found that existing
newshapekeyword is used quite a lot (GitHub search) so I don't think we should deprecate the old name. Here bothnewshapeandshapekeywords are supported.P.S. There's also[EDIT] Implemented as suggested in the comment.copykeyword in thereshape...