Skip to content

Commit

Permalink
Merge pull request #26292 from mtsokol/reshape-shape-keyword
Browse files Browse the repository at this point in the history
API: Add ``shape`` and ``copy`` arguments to ``numpy.reshape``
  • Loading branch information
ngoldbaum committed Apr 24, 2024
2 parents f8392ce + 4a264d1 commit 2da02ea
Show file tree
Hide file tree
Showing 11 changed files with 174 additions and 51 deletions.
1 change: 1 addition & 0 deletions doc/release/upcoming_changes/26292.new_feature.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
* `numpy.reshape` and `numpy.ndarray.reshape` now support ``shape`` and ``copy`` arguments.
2 changes: 1 addition & 1 deletion doc/source/user/absolute_beginners.rst
Original file line number Diff line number Diff line change
Expand Up @@ -425,7 +425,7 @@ this array to an array with three rows and two columns::

With ``np.reshape``, you can specify a few optional parameters::

>>> np.reshape(a, newshape=(1, 6), order='C')
>>> np.reshape(a, shape=(1, 6), order='C')
array([[0, 1, 2, 3, 4, 5]])

``a`` is the array to be reshaped.
Expand Down
12 changes: 10 additions & 2 deletions numpy/__init__.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -1713,11 +1713,19 @@ class ndarray(_ArrayOrScalarCommon, Generic[_ShapeType, _DType_co]):

@overload
def reshape(
self, shape: _ShapeLike, /, *, order: _OrderACF = ...
self,
shape: _ShapeLike,
/,
*,
order: _OrderACF = ...,
copy: None | bool = ...,
) -> ndarray[Any, _DType_co]: ...
@overload
def reshape(
self, *shape: SupportsIndex, order: _OrderACF = ...
self,
*shape: SupportsIndex,
order: _OrderACF = ...,
copy: None | bool = ...,
) -> ndarray[Any, _DType_co]: ...

@overload
Expand Down
2 changes: 1 addition & 1 deletion numpy/_core/_add_newdocs.py
Original file line number Diff line number Diff line change
Expand Up @@ -3824,7 +3824,7 @@

add_newdoc('numpy._core.multiarray', 'ndarray', ('reshape',
"""
a.reshape(shape, /, *, order='C')
a.reshape(shape, /, *, order='C', copy=None)
Returns an array containing the same data with a new shape.
Expand Down
49 changes: 38 additions & 11 deletions numpy/_core/fromnumeric.py
Original file line number Diff line number Diff line change
Expand Up @@ -206,28 +206,32 @@ def take(a, indices, axis=None, out=None, mode='raise'):
return _wrapfunc(a, 'take', indices, axis=axis, out=out, mode=mode)


def _reshape_dispatcher(a, newshape, order=None):
def _reshape_dispatcher(a, /, shape=None, *, newshape=None, order=None,
copy=None):
return (a,)


# not deprecated --- copy if necessary, view otherwise
@array_function_dispatch(_reshape_dispatcher)
def reshape(a, newshape, order='C'):
def reshape(a, /, shape=None, *, newshape=None, order='C', copy=None):
"""
Gives a new shape to an array without changing its data.
Parameters
----------
a : array_like
Array to be reshaped.
newshape : int or tuple of ints
shape : int or tuple of ints
The new shape should be compatible with the original shape. If
an integer, then the result will be a 1-D array of that length.
One shape dimension can be -1. In this case, the value is
inferred from the length of the array and remaining dimensions.
newshape : int or tuple of ints
.. deprecated:: 2.1
Replaced by ``shape`` argument. Retained for backward
compatibility.
order : {'C', 'F', 'A'}, optional
Read the elements of `a` using this index order, and place the
elements into the reshaped array using this index order. 'C'
Read the elements of ``a`` using this index order, and place the
elements into the reshaped array using this index order. 'C'
means to read / write the elements using C-like index order,
with the last axis index changing fastest, back to the first
axis index changing slowest. 'F' means to read / write the
Expand All @@ -236,8 +240,12 @@ def reshape(a, newshape, order='C'):
the 'C' and 'F' options take no account of the memory layout of
the underlying array, and only refer to the order of indexing.
'A' means to read / write the elements in Fortran-like index
order if `a` is Fortran *contiguous* in memory, C-like order
order if ``a`` is Fortran *contiguous* in memory, C-like order
otherwise.
copy : bool, optional
If ``True``, then the array data is copied. If ``None``, a copy will
only be made if it's required by ``order``. For ``False`` it raises
a ``ValueError`` if a copy cannot be avoided. Default: ``None``.
Returns
-------
Expand All @@ -255,9 +263,9 @@ def reshape(a, newshape, order='C'):
It is not always possible to change the shape of an array without copying
the data.
The `order` keyword gives the index ordering both for *fetching* the values
from `a`, and then *placing* the values into the output array.
For example, let's say you have an array:
The ``order`` keyword gives the index ordering both for *fetching*
the values from ``a``, and then *placing* the values into the output
array. For example, let's say you have an array:
>>> a = np.arange(6).reshape((3, 2))
>>> a
Expand Down Expand Up @@ -296,7 +304,26 @@ def reshape(a, newshape, order='C'):
[3, 4],
[5, 6]])
"""
return _wrapfunc(a, 'reshape', newshape, order=order)
if newshape is None and shape is None:
raise TypeError(
"reshape() missing 1 required positional argument: 'shape'")
if newshape is not None:
if shape is not None:
raise TypeError(
"You cannot specify 'newshape' and 'shape' arguments "
"at the same time.")
# Deprecated in NumPy 2.1, 2024-04-18
warnings.warn(
"`newshape` keyword argument is deprecated, "
"use `shape=...` or pass shape positionally instead. "
"(deprecated in NumPy 2.1)",
DeprecationWarning,
stacklevel=2,
)
shape = newshape
if copy is not None:
return _wrapfunc(a, 'reshape', shape, order=order, copy=copy)
return _wrapfunc(a, 'reshape', shape, order=order)


def _choose_dispatcher(a, choices, out=None, mode=None):
Expand Down
2 changes: 2 additions & 0 deletions numpy/_core/fromnumeric.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -94,12 +94,14 @@ def reshape(
a: _ArrayLike[_SCT],
newshape: _ShapeLike,
order: _OrderACF = ...,
copy: None | bool = ...,
) -> NDArray[_SCT]: ...
@overload
def reshape(
a: ArrayLike,
newshape: _ShapeLike,
order: _OrderACF = ...,
copy: None | bool = ...,
) -> NDArray[Any]: ...

@overload
Expand Down
10 changes: 6 additions & 4 deletions numpy/_core/src/multiarray/methods.c
Original file line number Diff line number Diff line change
Expand Up @@ -181,14 +181,16 @@ array_put(PyArrayObject *self, PyObject *args, PyObject *kwds)
static PyObject *
array_reshape(PyArrayObject *self, PyObject *args, PyObject *kwds)
{
static char *keywords[] = {"order", NULL};
static char *keywords[] = {"order", "copy", NULL};
PyArray_Dims newshape;
PyObject *ret;
NPY_ORDER order = NPY_CORDER;
NPY_COPYMODE copy = NPY_COPY_IF_NEEDED;
Py_ssize_t n = PyTuple_Size(args);

if (!NpyArg_ParseKeywords(kwds, "|O&", keywords,
PyArray_OrderConverter, &order)) {
if (!NpyArg_ParseKeywords(kwds, "|$O&O&", keywords,
PyArray_OrderConverter, &order,
PyArray_CopyConverter, &copy)) {
return NULL;
}

Expand All @@ -210,7 +212,7 @@ array_reshape(PyArrayObject *self, PyObject *args, PyObject *kwds)
goto fail;
}
}
ret = PyArray_Newshape(self, &newshape, order);
ret = _reshape_with_copy_arg(self, &newshape, order, copy);
npy_free_cache_dim_obj(newshape);
return ret;

Expand Down
87 changes: 56 additions & 31 deletions numpy/_core/src/multiarray/shape.c
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,14 @@ PyArray_Resize(PyArrayObject *self, PyArray_Dims *newshape, int refcheck,
NPY_NO_EXPORT PyObject *
PyArray_Newshape(PyArrayObject *self, PyArray_Dims *newdims,
NPY_ORDER order)
{
return _reshape_with_copy_arg(self, newdims, order, NPY_COPY_IF_NEEDED);
}


NPY_NO_EXPORT PyObject *
_reshape_with_copy_arg(PyArrayObject *array, PyArray_Dims *newdims,
NPY_ORDER order, NPY_COPYMODE copy)
{
npy_intp i;
npy_intp *dimensions = newdims->ptr;
Expand All @@ -212,64 +220,82 @@ PyArray_Newshape(PyArrayObject *self, PyArray_Dims *newdims,
int flags;

if (order == NPY_ANYORDER) {
order = PyArray_ISFORTRAN(self) ? NPY_FORTRANORDER : NPY_CORDER;
order = PyArray_ISFORTRAN(array) ? NPY_FORTRANORDER : NPY_CORDER;
}
else if (order == NPY_KEEPORDER) {
PyErr_SetString(PyExc_ValueError,
"order 'K' is not permitted for reshaping");
return NULL;
}
/* Quick check to make sure anything actually needs to be done */
if (ndim == PyArray_NDIM(self)) {
if (ndim == PyArray_NDIM(array) && copy != NPY_COPY_ALWAYS) {
same = NPY_TRUE;
i = 0;
while (same && i < ndim) {
if (PyArray_DIM(self,i) != dimensions[i]) {
if (PyArray_DIM(array, i) != dimensions[i]) {
same=NPY_FALSE;
}
i++;
}
if (same) {
return PyArray_View(self, NULL, NULL);
return PyArray_View(array, NULL, NULL);
}
}

/*
* fix any -1 dimensions and check new-dimensions against old size
*/
if (_fix_unknown_dimension(newdims, self) < 0) {
if (_fix_unknown_dimension(newdims, array) < 0) {
return NULL;
}
/*
* sometimes we have to create a new copy of the array
* in order to get the right orientation and
* because we can't just reuse the buffer with the
* data in the order it is in.
* Memory order doesn't depend on a copy/no-copy context.
* 'order' argument is always honored.
*/
Py_INCREF(self);
if (((order == NPY_CORDER && !PyArray_IS_C_CONTIGUOUS(self)) ||
(order == NPY_FORTRANORDER && !PyArray_IS_F_CONTIGUOUS(self)))) {
int success = 0;
success = _attempt_nocopy_reshape(self, ndim, dimensions,
newstrides, order);
if (success) {
/* no need to copy the array after all */
strides = newstrides;
if (copy == NPY_COPY_ALWAYS) {
PyObject *newcopy = PyArray_NewCopy(array, order);
if (newcopy == NULL) {
return NULL;
}
else {
PyObject *newcopy;
newcopy = PyArray_NewCopy(self, order);
Py_DECREF(self);
if (newcopy == NULL) {
array = (PyArrayObject *)newcopy;
}
else {
/*
* sometimes we have to create a new copy of the array
* in order to get the right orientation and
* because we can't just reuse the buffer with the
* data in the order it is in.
*/
Py_INCREF(array);
if (((order == NPY_CORDER && !PyArray_IS_C_CONTIGUOUS(array)) ||
(order == NPY_FORTRANORDER && !PyArray_IS_F_CONTIGUOUS(array)))) {
int success = 0;
success = _attempt_nocopy_reshape(array, ndim, dimensions,
newstrides, order);
if (success) {
/* no need to copy the array after all */
strides = newstrides;
}
else if (copy == NPY_COPY_NEVER) {
PyErr_SetString(PyExc_ValueError,
"Unable to avoid creating a copy while reshaping.");
Py_DECREF(array);
return NULL;
}
self = (PyArrayObject *)newcopy;
else {
PyObject *newcopy = PyArray_NewCopy(array, order);
Py_DECREF(array);
if (newcopy == NULL) {
return NULL;
}
array = (PyArrayObject *)newcopy;
}
}
}
/* We always have to interpret the contiguous buffer correctly */

/* Make sure the flags argument is set. */
flags = PyArray_FLAGS(self);
flags = PyArray_FLAGS(array);
if (ndim > 1) {
if (order == NPY_FORTRANORDER) {
flags &= ~NPY_ARRAY_C_CONTIGUOUS;
Expand All @@ -281,18 +307,17 @@ PyArray_Newshape(PyArrayObject *self, PyArray_Dims *newdims,
}
}

Py_INCREF(PyArray_DESCR(self));
Py_INCREF(PyArray_DESCR(array));
ret = (PyArrayObject *)PyArray_NewFromDescr_int(
Py_TYPE(self), PyArray_DESCR(self),
ndim, dimensions, strides, PyArray_DATA(self),
flags, (PyObject *)self, (PyObject *)self,
Py_TYPE(array), PyArray_DESCR(array),
ndim, dimensions, strides, PyArray_DATA(array),
flags, (PyObject *)array, (PyObject *)array,
_NPY_ARRAY_ENSURE_DTYPE_IDENTITY);
Py_DECREF(self);
Py_DECREF(array);
return (PyObject *)ret;
}



/* For backward compatibility -- Not recommended */

/*NUMPY_API
Expand Down
6 changes: 6 additions & 0 deletions numpy/_core/src/multiarray/shape.h
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
#ifndef NUMPY_CORE_SRC_MULTIARRAY_SHAPE_H_
#define NUMPY_CORE_SRC_MULTIARRAY_SHAPE_H_

#include "conversion_utils.h"

/*
* Creates a sorted stride perm matching the KEEPORDER behavior
* of the NpyIter object. Because this operates based on multiple
Expand All @@ -27,4 +29,8 @@ PyArray_SqueezeSelected(PyArrayObject *self, npy_bool *axis_flags);
NPY_NO_EXPORT PyObject *
PyArray_MatrixTranspose(PyArrayObject *ap);

NPY_NO_EXPORT PyObject *
_reshape_with_copy_arg(PyArrayObject *array, PyArray_Dims *newdims,
NPY_ORDER order, NPY_COPYMODE copy);

#endif /* NUMPY_CORE_SRC_MULTIARRAY_SHAPE_H_ */
Loading

0 comments on commit 2da02ea

Please sign in to comment.