8000 ENH: Add shape to *_like() array creation (#13046) · numpy/numpy@0c3d18f · GitHub
[go: up one dir, main page]

Skip to content

Commit 0c3d18f

Browse files
pentscheveric-wieser
authored andcommitted
ENH: Add shape to *_like() array creation (#13046)
* ENH: Added shape argument to *_like() array creation functions * ENH: C backend adjustments for shape argument on *_like() * TST: Added test for shape argument in *_like() functions * ENH: Added PyArray_NewLikeArrayWithShape() This change maintains backwards compatibility, rather than passing new arguments to PyArray_NewLikeArray(). * BUG: Fix for PyArray_NewLikeArrayWithShape strides and ndim == 0 Arrays created with new shapes should not take into consideration the original array's stride, and ndim == 0 should not be a case to ignore a new shape, as the caller may request a 0d array. * REL: Updates for C-API, version 1.17.x * Add comments to cversions.txt (new PyArray_NewLikeArrayWithShape function) * Increment C_API_VERSION to 1.17 in setup_common.py * Revert "REL: Updates for C-API, version 1.17.x" This reverts commit 807f512. * Revert exposing PyArray_NewLikeArrayWithShape on C-API * DOC: fix versionadded for *_like() shape argument * STY: add missing spaces in array initializers * ENH: empty_like raises ValueError This occurs when shape is defined and number of dimensions match but order is 'K'. * TST: test for exception of *_like() functions * DOC: release note for shape argument in *_like() functions * DOC: fix *_like() documentation on raises * BUG: *_like() raises for non-C/F-layout arrays * TST: change *_like() shapes to prevent NPY_RELAXED_STRIDE_DEBUG=1 failure * Move empty_like() exception to C implementation * Update *_like() ValueError documentation * Rearrange stride computation for *_like() if new shape and order='K' * Change handling of order= for *_like() - If order='K' try to keep, otherwise, order='C' is implied - Do not raise ValueError anymore * Fix *_like() tests
1 parent d96b446 commit 0c3d18f

File tree

7 files changed

+133
-30
lines changed

7 files changed

+133
-30
lines changed

doc/release/1.17.0-notes.rst

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,16 @@ New mode "empty" for ``np.pad``
134134
This mode pads an array to a desired shape without initializing the new
135135
entries.
136136

137+
138+
``np.empty_like`` and related functions now accept a ``shape`` argument
139+
-----------------------------------------------------------------------
140+
``np.empty_like``, ``np.full_like``, ``np.ones_like`` and ``np.zeros_like`` now
141+
accept a ``shape`` keyword argument, which can be used to create a new array
142+
as the prototype, overriding its shape as well. This is particularly useful
143+
when combined with the ``__array_function__`` protocol, allowing the creation
144+
of new arbitrary-shape arrays from NumPy-like libraries when such an array
145+
is used as the prototype.
146+
137147
Floating point scalars implement ``as_integer_ratio`` to match the builtin float
138148
--------------------------------------------------------------------------------
139149
This returns a (numerator, denominator) pair, which can be used to construct a

numpy/core/multiarray.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,9 +71,9 @@
7171

7272

7373
@array_function_from_c_func_and_dispatcher(_multiarray_umath.empty_like)
74-
def empty_like(prototype, dtype=None, order=None, subok=None):
74+
def empty_like(prototype, dtype=None, order=None, subok=None, shape=None):
7575
"""
76-
empty_like(prototype, dtype=None, order='K', subok=True)
76+
empty_like(prototype, dtype=None, order='K', subok=True, shape=None)
7777
7878
Return a new array with the same shape and type as a given array.
7979
@@ -97,6 +97,12 @@ def empty_like(prototype, dtype=None, order=None, subok=None):
9797
If True, then the newly created array will use the sub-class
9898
type of 'a', otherwise it will be a base-class array. Defaults
9999
to True.
100+
shape : int or sequence of ints, optional.
101+
Overrides the shape of the result. If order='K' and the number of
102+
dimensions is unchanged, will try to keep order, otherwise,
103+
order='C' is implied.
104+
105+
.. versionadded:: 1.17.0
100106
101107
Returns
102108
-------

numpy/core/numeric.py

Lines changed: 27 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -90,12 +90,12 @@ class ComplexWarning(RuntimeWarning):
9090
pass
9191

9292

93-
def _zeros_like_dispatcher(a, dtype=None, order=None, subok=None):
93+
def _zeros_like_dispatcher(a, dtype=None, order=None, subok=None, shape=None):
9494
return (a,)
9595

9696

9797
@array_function_dispatch(_zeros_like_dispatcher)
98-
def zeros_like(a, dtype=None, order='K', subok=True):
98+
def zeros_like(a, dtype=None, order='K', subok=True, shape=None):
9999
"""
100100
Return an array of zeros with the same shape and type as a given array.
101101
@@ -119,6 +119,12 @@ def zeros_like(a, dtype=None, order='K', subok=True):
119119
If True, then the newly created array will use the sub-class
120120
type of 'a', otherwise it will be a base-class array. Defaults
121121
to True.
122+
shape : int or sequence of ints, optional.
123+
Overrides the shape of the result. If order='K' and the number of
124+
dimensions is unchanged, will try to keep order, otherwise,
125+
order='C' is implied.
126+
127+
.. versionadded:: 1.17.0
122128
123129
Returns
124130
-------
@@ -150,7 +156,7 @@ def zeros_like(a, dtype=None, order='K', subok=True):
150156
array([0., 0., 0.])
151157
152158
"""
153-
res = empty_like(a, dtype=dtype, order=order, subok=subok)
159+
res = empty_like(a, dtype=dtype, order=order, subok=subok, shape=shape)
154160
# needed instead of a 0 to get same result as zeros for for string dtypes
155161
z = zeros(1, dtype=res.dtype)
156162
multiarray.copyto(res, z, casting='unsafe')
@@ -210,12 +216,12 @@ def ones(shape, dtype=None, order='C'):
210216
return a
211217

212218

213-
def _ones_like_dispatcher(a, dtype=None, order=None, subok=None):
219+
def _ones_like_dispatcher(a, dtype=None, order=None, subok=None, shape=None):
214220
return (a,)
215221

216222

217223
@array_function_dispatch(_ones_like_dispatcher)
218-
def ones_like(a, dtype=None, order='K', subok=True):
224+
def ones_like(a, dtype=None, order='K', subok=True, shape=None):
219225
"""
220226
Return an array of ones with the same shape and type as a given array.
221227
@@ -239,6 +245,12 @@ def ones_like(a, dtype=None, order='K', subok=True):
239245
If True, then the newly created array will use the sub-class
240246
type of 'a', otherwise it will be a base-class array. Defaults
241247
to True.
248+
shape : int or sequence of ints, optional.
249+
Overrides the shape of the result. If order='K' and the number of
250+
dimensions is unchanged, will try to keep order, otherwise,
251+
order='C' is implied.
252+
253+
.. versionadded:: 1.17.0
242254
243255
Returns
244256
-------
@@ -270,7 +282,7 @@ def ones_like(a, dtype=None, order='K', subok=True):
270282
array([1., 1., 1.])
271283
272284
"""
273-
res = empty_like(a, dtype=dtype, order=order, subok=subok)
285+
res = empty_like(a, dtype=dtype, order=order, subok=subok, shape=shape)
274286
multiarray.copyto(res, 1, casting='unsafe')
275287
return res
276288

@@ -322,12 +334,12 @@ def full(shape, fill_value, dtype=None, order='C'):
322334
return a
323335

324336

325-
def _full_like_dispatcher(a, fill_value, dtype=None, order=None, subok=None):
337+
def _full_like_dispatcher(a, fill_value, dtype=None, order=None, subok=None, shape=None):
326338
return (a,)
327339

328340

329341
@array_function_dispatch(_full_like_dispatcher)
330-
def full_like(a, fill_value, dtype=None, order='K', subok=True):
342+
def full_like(a, fill_value, dtype=None, order='K', subok=True, shape=None):
331343
"""
332344
Return a full array with the same shape and type as a given array.
333345
@@ -349,6 +361,12 @@ def full_like(a, fill_value, dtype=None, order='K', subok=True):
349361
If True, then the newly created array will use the sub-class
350362
type of 'a', otherwise it will be a base-class array. Defaults
351363
to True.
364+
shape : int or sequence of ints, optional.
365+
Overrides the shape of the result. If order='K' and the number of
366+
dimensions is unchanged, will try to keep order, otherwise,
367+
order='C' is implied.
368+
369+
.. versionadded:: 1.17.0
352370
353371
Returns
354372
-------
@@ -379,7 +397,7 @@ def full_like(a, fill_value, dtype=None, order='K', subok=True):
379397
array([0.1, 0.1, 0.1, 0.1, 0.1, 0.1])
380398
381399
"""
382-
res = empty_like(a, dtype=dtype, order=order, subok=subok)
400+
res = empty_like(a, dtype=dtype, order=order, subok=subok, shape=shape)
383401
multiarray.copyto(res, fill_value, casting='unsafe')
384402
return res
385403

numpy/core/src/multiarray/ctors.c

Lines changed: 41 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1183,28 +1183,37 @@ PyArray_NewFromDescrAndBase(
11831183
flags, obj, base, 0, 0);
11841184
}
11851185

1186-
/*NUMPY_API
1186+
/*
11871187
* Creates a new array with the same shape as the provided one,
1188-
* with possible memory layout order and data type changes.
1188+
* with possible memory layout order, data type and shape changes.
11891189
*
11901190
* prototype - The array the new one should be like.
11911191
* order - NPY_CORDER - C-contiguous result.
11921192
* NPY_FORTRANORDER - Fortran-contiguous result.
11931193
* NPY_ANYORDER - Fortran if prototype is Fortran, C otherwise.
11941194
* NPY_KEEPORDER - Keeps the axis ordering of prototype.
11951195
* dtype - If not NULL, overrides the data type of the result.
1196+
* ndim - If not 0 and dims not NULL, overrides the shape of the result.
1197+
* dims - If not NULL and ndim not 0, overrides the shape of the result.
11961198
* subok - If 1, use the prototype's array subtype, otherwise
11971199
* always create a base-class array.
11981200
*
11991201
* NOTE: If dtype is not NULL, steals the dtype reference. On failure or when
12001202
* dtype->subarray is true, dtype will be decrefed.
12011203
*/
12021204
NPY_NO_EXPORT PyObject *
1203-
PyArray_NewLikeArray(PyArrayObject *prototype, NPY_ORDER order,
1204-
PyArray_Descr *dtype, int subok)
1205+
PyArray_NewLikeArrayWithShape(PyArrayObject *prototype, NPY_ORDER order,
1206+
PyArray_Descr *dtype, int ndim, npy_intp *dims, int subok)
12051207
{
12061208
PyObject *ret = NULL;
1207-
int ndim = PyArray_NDIM(prototype);
1209+
1210+
if (dims == NULL) {
1211+
ndim = PyArray_NDIM(prototype);
1212+
dims = PyArray_DIMS(prototype);
1213+
}
1214+
else if (order == NPY_KEEPORDER && (ndim != PyArray_NDIM(prototype))) {
1215+
order = NPY_CORDER;
1216+
}
12081217

12091218
/* If no override data type, use the one from the prototype */
12101219
if (dtype == NULL) {
@@ -1237,7 +1246,7 @@ PyArray_NewLikeArray(PyArrayObject *prototype, NPY_ORDER order,
12371246
ret = PyArray_NewFromDescr(subok ? Py_TYPE(prototype) : &PyArray_Type,
12381247
dtype,
12391248
ndim,
1240-
PyArray_DIMS(prototype),
1249+
dims,
12411250
NULL,
12421251
NULL,
12431252
order,
@@ -1246,11 +1255,10 @@ PyArray_NewLikeArray(PyArrayObject *prototype, NPY_ORDER order,
12461255
/* KEEPORDER needs some analysis of the strides */
12471256
else {
12481257
npy_intp strides[NPY_MAXDIMS], stride;
1249-
npy_intp *shape = PyArray_DIMS(prototype);
12501258
npy_stride_sort_item strideperm[NPY_MAXDIMS];
12511259
int idim;
12521260

1253-
PyArray_CreateSortedStridePerm(PyArray_NDIM(prototype),
1261+
PyArray_CreateSortedStridePerm(ndim,
12541262
PyArray_STRIDES(prototype),
12551263
strideperm);
12561264

@@ -1259,14 +1267,14 @@ PyArray_NewLikeArray(PyArrayObject *prototype, NPY_ORDER order,
12591267
for (idim = ndim-1; idim >= 0; --idim) {
12601268
npy_intp i_perm = strideperm[idim].perm;
12611269
strides[i_perm] = stride;
1262-
stride *= shape[i_perm];
1270+
stride *= dims[i_perm];
12631271
}
12641272

12651273
/* Finally, allocate the array */
12661274
ret = PyArray_NewFromDescr(subok ? Py_TYPE(prototype) : &PyArray_Type,
12671275
dtype,
12681276
ndim,
1269-
shape,
1277+
dims,
12701278
strides,
12711279
NULL,
12721280
0,
@@ -1276,6 +1284,29 @@ PyArray_NewLikeArray(PyArrayObject *prototype, NPY_ORDER order,
12761284
return ret;
12771285
}
12781286

1287+
/*NUMPY_API
1288+
* Creates a new array with the same shape as the provided one,
1289+
* with possible memory layout order and data type changes.
1290+
*
1291+
* prototype - The array the new one should be like.
1292+
* order - NPY_CORDER - C-contiguous result.
1293+
* NPY_FORTRANORDER - Fortran-contiguous result.
1294+
* NPY_ANYORDER - Fortran if prototype is Fortran, C otherwise.
1295+
* NPY_KEEPORDER - Keeps the axis ordering of prototype.
1296+
* dtype - If not NULL, overrides the data type of the result.
1297+
* subok - If 1, use the prototype's array subtype, otherwise
1298+
* always create a base-class array.
1299+
*
1300+
* NOTE: If dtype is not NULL, steals the dtype reference. On failure or when
1301+
* dtype->subarray is true, dtype will be decrefed.
1302+
*/
1303+
NPY_NO_EXPORT PyObject *
1304+
PyArray_NewLikeArray(PyArrayObject *prototype, NPY_ORDER order,
1305+
PyArray_Descr *dtype, int subok)
1306+
{
1307+
return PyArray_NewLikeArrayWithShape(prototype, order, dtype, 0, NULL, subok);
1308+
}
1309+
12791310
/*NUMPY_API
12801311
* Generic new array creation routine.
12811312
*/

numpy/core/src/multiarray/ctors.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,10 @@ PyArray_NewFromDescr_int(PyTypeObject *subtype, PyArray_Descr *descr, int nd,
1818
int flags, PyObject *obj, PyObject *base, int zeroed,
1919
int allow_emptystring);
2020

21+
NPY_NO_EXPORT PyObject *
22+
PyArray_NewLikeArrayWithShape(PyArrayObject *prototype, NPY_ORDER order,
23+
PyArray_Descr *dtype, int ndim, npy_intp *dims, int subok);
24+
2125
NPY_NO_EXPORT PyObject *PyArray_New(PyTypeObject *, int nd, npy_intp *,
2226
int, npy_intp *, void *, int, int, PyObject *);
2327

numpy/core/src/multiarray/multiarraymodule.c

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1758,7 +1758,7 @@ static PyObject *
17581758
array_copyto(PyObject *NPY_UNUSED(ignored), PyObject *args, PyObject *kwds)
17591759
{
17601760

1761-
static char *kwlist[] = {"dst","src","casting","where",NULL};
1761+
static char *kwlist[] = {"dst", "src", "casting", "where", NULL};
17621762
PyObject *wheremask_in = NULL;
17631763
PyArrayObject *dst = NULL, *src = NULL, *wheremask = NULL;
17641764
NPY_CASTING casting = NPY_SAME_KIND_CASTING;
@@ -1803,7 +1803,7 @@ static PyObject *
18031803
array_empty(PyObject *NPY_UNUSED(ignored), PyObject *args, PyObject *kwds)
18041804
{
18051805

1806-
static char *kwlist[] = {"shape","dtype","order",NULL};
1806+
static char *kwlist[] = {"shape", "dtype", "order", NULL};
18071807
PyArray_Descr *typecode = NULL;
18081808
PyArray_Dims shape = {NULL, 0};
18091809
NPY_ORDER order = NPY_CORDER;
@@ -1846,23 +1846,28 @@ static PyObject *
18461846
array_empty_like(PyObject *NPY_UNUSED(ignored), PyObject *args, PyObject *kwds)
18471847
{
18481848

1849-
static char *kwlist[] = {"prototype","dtype","order","subok",NULL};
1849+
static char *kwlist[] = {"prototype", "dtype", "order", "subok", "shape", NULL};
18501850
PyArrayObject *prototype = NULL;
18511851
PyArray_Descr *dtype = NULL;
18521852
NPY_ORDER order = NPY_KEEPORDER;
18531853
PyArrayObject *ret = NULL;
18541854
int subok = 1;
1855+
PyArray_Dims shape = {NULL, 0};
18551856

1856-
if (!PyArg_ParseTupleAndKeywords(args, kwds, "O&|O&O&i:empty_like", kwlist,
1857+
if (!PyArg_ParseTupleAndKeywords(args, kwds, "O&|O&O&iO&:empty_like", kwlist,
18571858
&PyArray_Converter, &prototype,
18581859
&PyArray_DescrConverter2, &dtype,
18591860
&PyArray_OrderConverter, &order,
1860-
&subok)) {
1861+
&subok,
1862+
&PyArray_IntpConverter, &shape)) {
18611863
goto fail;
18621864
}
18631865
/* steals the reference to dtype if it's not NULL */
1864-
ret = (PyArrayObject *)PyArray_NewLikeArray(prototype,
1865-
order, dtype, subok);
1866+
ret = (PyArrayObject *)PyArray_NewLikeArrayWithShape(prototype, order, dtype,
1867+
shape.len, shape.ptr, subok);
1868+
if (!ret) {
1869+
goto fail;
1870+
}
18661871
Py_DECREF(prototype);
18671872

18681873
return (PyObject *)ret;
@@ -1881,7 +1886,7 @@ static PyObject *
18811886
array_scalar(PyObject *NPY_UNUSED(ignored), PyObject *args, PyObject *kwds)
18821887
{
18831888

1884-
static char *kwlist[] = {"dtype","obj", NULL};
1889+
static char *kwlist[] = {"dtype", "obj", NULL};
18851890
PyArray_Descr *typecode;
18861891
PyObject *obj = NULL, *tmpobj = NULL;
18871892
int alloc = 0;
@@ -1957,7 +1962,7 @@ array_scalar(PyObject *NPY_UNUSED(ignored), PyObject *args, PyObject *kwds)
19571962
static PyObject *
19581963
array_zeros(PyObject *NPY_UNUSED(ignored), PyObject *args, PyObject *kwds)
19591964
{
1960-
static char *kwlist[] = {"shape","dtype","order",NULL};
1965+
static char *kwlist[] = {"shape", "dtype", "order", NULL};
19611966
PyArray_Descr *typecode = NULL;
19621967
PyArray_Dims shape = {NULL, 0};
19631968
NPY_ORDER order = NPY_CORDER;

numpy/core/tests/test_numeric.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2157,6 +2157,7 @@ def setup(self):
21572157
(np.arange(24).reshape(2, 3, 4).swapaxes(0, 1), None),
21582158
(np.arange(24).reshape(4, 3, 2).swapaxes(0, 1), '?'),
21592159
]
2160+
self.shapes = [(5,), (5,6,), (5,6,7,)]
21602161

21612162
def compare_array_value(self, dz, value, fill_value):
21622163
if value is not None:
@@ -2222,6 +2223,34 @@ def check_like_function(self, like_function, value, fill_value=False):
22222223
assert_equal(dz.dtype, np.dtype(dtype))
22232224
self.compare_array_value(dz, value, fill_value)
22242225

2226+
# Test the 'shape' parameter
2227+
for s in self.shapes:
2228+
for o in 'CFA':
2229+
sz = like_function(d, dtype=dtype, shape=s, order=o,
2230+
**fill_kwarg)
2231+
assert_equal(sz.shape, s)
2232+
if dtype is None:
2233+
assert_equal(sz.dtype, d.dtype)
2234+
else:
2235+
assert_equal(sz.dtype, np.dtype(dtype))
2236+
if o == 'C' or (o == 'A' and d.flags.c_contiguous):
2237+
assert_(sz.flags.c_contiguous)
2238+
elif o == 'F' or (o == 'A' and d.flags.f_contiguous):
2239+
assert_(sz.flags.f_contiguous)
2240+
self.compare_array_value(sz, value, fill_value)
2241+
2242+
if (d.ndim != len(s)):
2243+
assert_equal(np.argsort(like_function(d, dtype=dtype,
2244+
shape=s, order='K',
2245+
**fill_kwarg).strides),
2246+
np.argsort(np.empty(s, dtype=dtype,
2247+
order='C').strides))
2248+
else:
2249+
assert_equal(np.argsort(like_function(d, dtype=dtype,
2250+
shape=s, order='K',
2251+
**fill_kwarg).strides),
2252+
np.argsort(d.strides))
2253+
22252254
# Test the 'subok' parameter
22262255
class MyNDArray(np.ndarray):
22272256
pass

0 commit comments

Comments
 (0)
0