Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit e3bf8e5

Browse files
committed
API: Add shape argument to numpy.reshape
1 parent d567754 commit e3bf8e5

File tree

9 files changed

+152
-51
lines changed

9 files changed

+152
-51
lines changed

numpy/__init__.pyi

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1713,11 +1713,19 @@ class ndarray(_ArrayOrScalarCommon, Generic[_ShapeType, _DType_co]):
17131713

17141714
@overload
17151715
def reshape(
1716-
self, shape: _ShapeLike, /, *, order: _OrderACF = ...
1716+
self,
1717+
shape: _ShapeLike,
1718+
/,
1719+
*,
1720+
order: _OrderACF = ...,
1721+
copy: None | bool = ...,
17171722
) -> ndarray[Any, _DType_co]: ...
17181723
@overload
17191724
def reshape(
1720-
self, *shape: SupportsIndex, order: _OrderACF = ...
1725+
self,
1726+
*shape: SupportsIndex,
1727+
order: _OrderACF = ...,
1728+
copy: None | bool = ...,
17211729
) -> ndarray[Any, _DType_co]: ...
17221730

17231731
@overload

numpy/_core/_add_newdocs.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3824,7 +3824,7 @@
38243824

38253825
add_newdoc('numpy._core.multiarray', 'ndarray', ('reshape',
38263826
"""
3827-
a.reshape(shape, /, *, order='C')
3827+
a.reshape(shape, /, *, order='C', copy=None)
38283828
38293829
Returns an array containing the same data with a new shape.
38303830

numpy/_core/fromnumeric.py

Lines changed: 26 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -206,13 +206,13 @@ def take(a, indices, axis=None, out=None, mode='raise'):
206206
return _wrapfunc(a, 'take', indices, axis=axis, out=out, mode=mode)
207207

208208

209-
def _reshape_dispatcher(a, newshape, order=None):
209+
def _reshape_dispatcher(a, /, newshape=None, shape=None, *,
210+
order=None, copy=None):
210211
return (a,)
211212

212213

213-
# not deprecated --- copy if necessary, view otherwise
214214
@array_function_dispatch(_reshape_dispatcher)
215-
def reshape(a, newshape, order='C'):
215+
def reshape(a, /, newshape=None, shape=None, *, order='C', copy=None):
216216
"""
217217
Gives a new shape to an array without changing its data.
218218
@@ -221,13 +221,16 @@ def reshape(a, newshape, order='C'):
221221
a : array_like
222222
Array to be reshaped.
223223
newshape : int or tuple of ints
224+
Replaced by ``shape`` argument. Retained for backward
225+
compatibility.
226+
shape : int or tuple of ints
224227
The new shape should be compatible with the original shape. If
225228
an integer, then the result will be a 1-D array of that length.
226229
One shape dimension can be -1. In this case, the value is
227230
inferred from the length of the array and remaining dimensions.
228231
order : {'C', 'F', 'A'}, optional
229-
Read the elements of `a` using this index order, and place the
230-
elements into the reshaped array using this index order. 'C'
232+
Read the elements of ``a`` using this index order, and place the
233+
elements into the reshaped array using this index order. 'C'
231234
means to read / write the elements using C-like index order,
232235
with the last axis index changing fastest, back to the first
233236
axis index changing slowest. 'F' means to read / write the
@@ -236,8 +239,12 @@ def reshape(a, newshape, order='C'):
236239
the 'C' and 'F' options take no account of the memory layout of
237240
the underlying array, and only refer to the order of indexing.
238241
'A' means to read / write the elements in Fortran-like index
239-
order if `a` is Fortran *contiguous* in memory, C-like order
242+
order if ``a`` is Fortran *contiguous* in memory, C-like order
240243
otherwise.
244+
copy : bool, optional
245+
If ``True``, then the array data is copied. If ``None``, a copy will
246+
only be made if it's required by ``order``. For ``False`` it raises
247+
a ``ValueError`` if a copy cannot be avoided. Default: ``None``.
241248
242249
Returns
243250
-------
@@ -255,9 +262,9 @@ def reshape(a, newshape, order='C'):
255262
It is not always possible to change the shape of an array without copying
256263
the data.
257264
258-
The `order` keyword gives the index ordering both for *fetching* the values
259-
from `a`, and then *placing* the values into the output array.
260-
For example, let's say you have an array:
265+
The ``order`` keyword gives the index ordering both for *fetching*
266+
the values from ``a``, and then *placing* the values into the output
267+
array. For example, let's say you have an array:
261268
262269
>>> a = np.arange(6).reshape((3, 2))
263270
>>> a
@@ -296,7 +303,16 @@ def reshape(a, newshape, order='C'):
296303
[3, 4],
297304
[5, 6]])
298305
"""
299-
return _wrapfunc(a, 'reshape', newshape, order=order)
306+
if newshape is None and shape is None:
307+
raise TypeError(
308+
"reshape() missing 1 required positional argument: 'shape'")
309+
if newshape is not None and shape is not None:
310+
raise ValueError(
311+
"You cannot specify 'newshape' and 'shape' arguments "
312+
"at the same time.")
313+
if shape is not None:
314+
newshape = shape
315+
return _wrapfunc(a, 'reshape', newshape, order=order, copy=copy)
300316

301317

302318
def _choose_dispatcher(a, choices, out=None, mode=None):

numpy/_core/fromnumeric.pyi

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,12 +94,14 @@ def reshape(
9494
a: _ArrayLike[_SCT],
9595
newshape: _ShapeLike,
9696
order: _OrderACF = ...,
97+
copy: None | bool = ...,
9798
) -> NDArray[_SCT]: ...
9899
@overload
99100
def reshape(
100101
a: ArrayLike,
101102
newshape: _ShapeLike,
102103
order: _OrderACF = ...,
104+
copy: None | bool = ...,
103105
) -> NDArray[Any]: ...
104106

105107
@overload

numpy/_core/src/multiarray/methods.c

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -181,14 +181,16 @@ array_put(PyArrayObject *self, PyObject *args, PyObject *kwds)
181181
static PyObject *
182182
array_reshape(PyArrayObject *self, PyObject *args, PyObject *kwds)
183183
{
184-
static char *keywords[] = {"order", NULL};
184+
static char *keywords[] = {"order", "copy", NULL};
185185
PyArray_Dims newshape;
186186
PyObject *ret;
187187
NPY_ORDER order = NPY_CORDER;
188+
NPY_COPYMODE copy = NPY_COPY_IF_NEEDED;
188189
Py_ssize_t n = PyTuple_Size(args);
189190

190-
if (!NpyArg_ParseKeywords(kwds, "|O&", keywords,
191-
PyArray_OrderConverter, &order)) {
191+
if (!NpyArg_ParseKeywords(kwds, "|$O&O&", keywords,
192+
PyArray_OrderConverter, &order,
193+
PyArray_CopyConverter, &copy)) {
192194
return NULL;
193195
}
194196

@@ -210,7 +212,7 @@ array_reshape(PyArrayObject *self, PyObject *args, PyObject *kwds)
210212
goto fail;
211213
}
212214
}
213-
ret = PyArray_Newshape(self, &newshape, order);
215+
ret = _reshape_with_copy_arg(self, &newshape, order, copy);
214216
npy_free_cache_dim_obj(newshape);
215217
return ret;
216218

numpy/_core/src/multiarray/shape.c

Lines changed: 54 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,14 @@ PyArray_Resize(PyArrayObject *self, PyArray_Dims *newshape, int refcheck,
201201
NPY_NO_EXPORT PyObject *
202202
PyArray_Newshape(PyArrayObject *self, PyArray_Dims *newdims,
203203
NPY_ORDER order)
204+
{
205+
return _reshape_with_copy_arg(self, newdims, order, NPY_COPY_IF_NEEDED);
206+
}
207+
208+
209+
NPY_NO_EXPORT PyObject *
210+
_reshape_with_copy_arg(PyArrayObject *array, PyArray_Dims *newdims,
211+
NPY_ORDER order, NPY_COPYMODE copy)
204212
{
205213
npy_intp i;
206214
npy_intp *dimensions = newdims->ptr;
@@ -212,64 +220,78 @@ PyArray_Newshape(PyArrayObject *self, PyArray_Dims *newdims,
212220
int flags;
213221

214222
if (order == NPY_ANYORDER) {
215-
order = PyArray_ISFORTRAN(self) ? NPY_FORTRANORDER : NPY_CORDER;
223+
order = PyArray_ISFORTRAN(array) ? NPY_FORTRANORDER : NPY_CORDER;
216224
}
217225
else if (order == NPY_KEEPORDER) {
218226
PyErr_SetString(PyExc_ValueError,
219227
"order 'K' is not permitted for reshaping");
220228
return NULL;
221229
}
222230
/* Quick check to make sure anything actually needs to be done */
223-
if (ndim == PyArray_NDIM(self)) {
231+
if (ndim == PyArray_NDIM(array) && copy != NPY_COPY_ALWAYS) {
224232
same = NPY_TRUE;
225233
i = 0;
226234
while (same && i < ndim) {
227-
if (PyArray_DIM(self,i) != dimensions[i]) {
235+
if (PyArray_DIM(array, i) != dimensions[i]) {
228236
same=NPY_FALSE;
229237
}
230238
i++;
231239
}
232240
if (same) {
233-
return PyArray_View(self, NULL, NULL);
241+
return PyArray_View(array, NULL, NULL);
234242
}
235243
}
236244

237245
/*
238246
* fix any -1 dimensions and check new-dimensions against old size
239247
*/
240-
if (_fix_unknown_dimension(newdims, self) < 0) {
248+
if (_fix_unknown_dimension(newdims, array) < 0) {
241249
return NULL;
242250
}
243-
/*
244-
* sometimes we have to create a new copy of the array
245-
* in order to get the right orientation and
246-
* because we can't just reuse the buffer with the
247-
* data in the order it is in.
248-
*/
249-
Py_INCREF(self);
250-
if (((order == NPY_CORDER && !PyArray_IS_C_CONTIGUOUS(self)) ||
251-
(order == NPY_FORTRANORDER && !PyArray_IS_F_CONTIGUOUS(self)))) {
252-
int success = 0;
253-
success = _attempt_nocopy_reshape(self, ndim, dimensions,
254-
newstrides, order);
255-
if (success) {
256-
/* no need to copy the array after all */
257-
strides = newstrides;
251+
if (copy == NPY_COPY_ALWAYS) {
252+
PyObject *newcopy = PyArray_NewCopy(array, order);
253+
if (newcopy == NULL) {
254+
return NULL;
258255
}
259-
else {
260-
PyObject *newcopy;
261-
newcopy = PyArray_NewCopy(self, order);
262-
Py_DECREF(self);
263-
if (newcopy == NULL) {
256+
array = (PyArrayObject *)newcopy;
257+
}
258+
else {
259+
/*
260+
* sometimes we have to create a new copy of the array
261+
* in order to get the right orientation and
262+
* because we can't just reuse the buffer with the
263+
* data in the order it is in.
264+
*/
265+
Py_INCREF(array);
266+
if (((order == NPY_CORDER && !PyArray_IS_C_CONTIGUOUS(array)) ||
267+
(order == NPY_FORTRANORDER && !PyArray_IS_F_CONTIGUOUS(array)))) {
268+
int success = 0;
269+
success = _attempt_nocopy_reshape(array, ndim, dimensions,
270+
newstrides, order);
271+
if (success) {
272+
/* no need to copy the array after all */
273+
strides = newstrides;
274+
}
275+
else if (copy == NPY_COPY_NEVER) {
276+
PyErr_SetString(PyExc_ValueError,
277+
"Unable to avoid creating a copy while reshaping.");
278+
Py_DECREF(array);
264279
return NULL;
265280
}
266-
self = (PyArrayObject *)newcopy;
281+
else {
282+
PyObject *newcopy = PyArray_NewCopy(array, order);
283+
Py_DECREF(array);
284+
if (newcopy == NULL) {
285+
return NULL;
286+
}
287+
array = (PyArrayObject *)newcopy;
288+
}
267289
}
268290
}
269291
/* We always have to interpret the contiguous buffer correctly */
270292

271293
/* Make sure the flags argument is set. */
272-
flags = PyArray_FLAGS(self);
294+
flags = PyArray_FLAGS(array);
273295
if (ndim > 1) {
274296
if (order == NPY_FORTRANORDER) {
275297
flags &= ~NPY_ARRAY_C_CONTIGUOUS;
@@ -281,18 +303,17 @@ PyArray_Newshape(PyArrayObject *self, PyArray_Dims *newdims,
281303
}
282304
}
283305

284-
Py_INCREF(PyArray_DESCR(self));
306+
Py_INCREF(PyArray_DESCR(array));
285307
ret = (PyArrayObject *)PyArray_NewFromDescr_int(
286-
Py_TYPE(self), PyArray_DESCR(self),
287-
ndim, dimensions, strides, PyArray_DATA(self),
288-
flags, (PyObject *)self, (PyObject *)self,
308+
Py_TYPE(array), PyArray_DESCR(array),
309+
ndim, dimensions, strides, PyArray_DATA(array),
310+
flags, (PyObject *)array, (PyObject *)array,
289311
_NPY_ARRAY_ENSURE_DTYPE_IDENTITY);
290-
Py_DECREF(self);
312+
Py_DECREF(array);
291313
return (PyObject *)ret;
292314
}
293315

294316

295-
296317
/* For backward compatibility -- Not recommended */
297318

298319
/*NUMPY_API

numpy/_core/src/multiarray/shape.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
#ifndef NUMPY_CORE_SRC_MULTIARRAY_SHAPE_H_
22
#define NUMPY_CORE_SRC_MULTIARRAY_SHAPE_H_
33

4+
#include "conversion_utils.h"
5+
46
/*
57
* Creates a sorted stride perm matching the KEEPORDER behavior
68
* of the NpyIter object. Because this operates based on multiple
@@ -27,4 +29,8 @@ PyArray_SqueezeSelected(PyArrayObject *self, npy_bool *axis_flags);
2729
NPY_NO_EXPORT PyObject *
2830
PyArray_MatrixTranspose(PyArrayObject *ap);
2931

32+
NPY_NO_EXPORT PyObject *
33+
_reshape_with_copy_arg(PyArrayObject *array, PyArray_Dims *newdims,
34+
NPY_ORDER order, NPY_COPYMODE copy);
35+
3036
#endif /* NUMPY_CORE_SRC_MULTIARRAY_SHAPE_H_ */

numpy/_core/tests/test_numeric.py

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,52 @@ def test_reshape(self):
165165
tgt = [[1, 2, 3, 4, 5, 6], [7, 8, 9, 10, 11, 12]]
166166
assert_equal(np.reshape(arr, (2, 6)), tgt)
167167

168+
def test_reshape_shape_arg(self):
169+
arr = np.arange(12)
170+
shape = (3, 4)
171+
expected = arr.reshape(shape)
172+
173+
with pytest.raises(
174+
ValueError,
175+
match="You cannot specify 'newshape' and 'shape' "
176+
"arguments at the same time."
177+
):
178+
np.reshape(arr, newshape=shape, shape=shape)
179+
with pytest.raises(
180+
TypeError,
181+
match=r"reshape\(\) missing 1 required positional "
182+
"argument: 'shape'"
183+
):
184+
np.reshape(arr)
185+
186+
assert_equal(np.reshape(arr, shape), expected)
187+
assert_equal(np.reshape(arr, shape, order="C"), expected)
188+
assert_equal(np.reshape(arr, newshape=shape), expected)
189+
assert_equal(np.reshape(arr, shape=shape), expected)
190+
assert_equal(np.reshape(arr, shape=shape, order="C"), expected)
191+
192+
def test_reshape_copy_arg(self):
193+
arr = np.arange(24).reshape(2, 3, 4)
194+
arr_f_ord = np.array(arr, order="F")
195+
shape = (12, 2)
196+
197+
assert np.shares_memory(np.reshape(arr, shape), arr)
198+
assert np.shares_memory(np.reshape(arr, shape, order="C"), arr)
199+
assert np.shares_memory(np.reshape(arr_f_ord, shape, order="F"), arr_f_ord)
200+
assert np.shares_memory(np.reshape(arr, shape, copy=None), arr)
201+
assert np.shares_memory(np.reshape(arr, shape, copy=False), arr)
202+
assert np.shares_memory(arr.reshape(shape, copy=False), arr)
203+
assert not np.shares_memory(np.reshape(arr, shape, copy=True), arr)
204+
assert not np.shares_memory(np.reshape(arr, shape, order="C", copy=True), arr)
205+
assert not np.shares_memory(np.reshape(arr, shape, order="F", copy=True), arr)
206+
assert not np.shares_memory(np.reshape(arr, shape, order="F", copy=None), arr)
207+
208+
err_msg = "Unable to avoid creating a copy while reshaping."
209+
with pytest.raises(ValueError, match=err_msg):
210+
np.reshape(arr, shape, order="F", copy=False)
211+
with pytest.raises(ValueError, match=err_msg):
212+
np.reshape(arr_f_ord, shape, order="C", copy=False)
213+
168214
def test_round(self):
169215
arr = [1.56, 72.54, 6.35, 3.25]
170216
tgt = [1.6, 72.5, 6.4, 3.2]

tools/ci/array-api-skips.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ array_api_tests/test_operators_and_elementwise_functions.py::test_ceil
77
array_api_tests/test_operators_and_elementwise_functions.py::test_floor
88
array_api_tests/test_operators_and_elementwise_functions.py::test_trunc
99

10-
# 'newshape' should be named 'shape'
10+
# 'shape' arg is present. 'newshape' is retained for backward compat.
1111
array_api_tests/test_signatures.py::test_func_signature[reshape]
1212

1313
# missing 'descending' keyword arguments

0 commit comments

Comments
 (0)