8000 Merge pull request #19173 from czgdp1807/never_copy · numpy/numpy@7125cdf · GitHub
[go: up one dir, main page]

Skip to content

Commit 7125cdf

Browse files
authored
Merge pull request #19173 from czgdp1807/never_copy
ENH: Add support for copy modes to NumPy
2 parents a181350 + f058aea commit 7125cdf

20 files changed

+513
-66
lines changed

numpy/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,8 @@
110110
import warnings
111111

112112
from ._globals import (
113-
ModuleDeprecationWarning, VisibleDeprecationWarning, _NoValue
113+
ModuleDeprecationWarning, VisibleDeprecationWarning,
114+
_NoValue, _CopyMode
114115
)
115116

116117
# We first need to detect if we're being called as part of the numpy setup

numpy/__init__.pyi

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import mmap
55
import ctypes as ct
66
import array as _array
77
import datetime as dt
8+
import enum
89
from abc import abstractmethod
910
from types import TracebackType, MappingProxyType
1011
from contextlib import ContextDecorator
@@ -1745,7 +1746,7 @@ class ndarray(_ArrayOrScalarCommon, Generic[_ShapeType, _DType_co]):
17451746
order: _OrderKACF = ...,
17461747
casting: _CastingKind = ...,
17471748
subok: bool = ...,
1748-
copy: bool = ...,
1749+
copy: bool | _CopyMode = ...,
17491750
) -> NDArray[_ScalarType]: ...
17501751
@overload
17511752
def astype(
@@ -1754,7 +1755,7 @@ class ndarray(_ArrayOrScalarCommon, Generic[_ShapeType, _DType_co]):
17541755
order: _OrderKACF = ...,
17551756
casting: _CastingKind = ...,
17561757
subok: bool = ...,
1757-
copy: bool = ...,
1758+
copy: bool | _CopyMode = ...,
17581759
) -> NDArray[Any]: ...
17591760

17601761
@overload
@@ -2495,7 +2496,7 @@ class generic(_ArrayOrScalarCommon):
24952496
order: _OrderKACF = ...,
24962497
casting: _CastingKind = ...,
24972498
subok: bool = ...,
2498-
copy: bool = ...,
2499+
copy: bool | _CopyMode = ...,
24992500
) -> _ScalarType: ...
25002501
@overload
25012502
def astype(
@@ -2504,7 +2505,7 @@ class generic(_ArrayOrScalarCommon):
25042505
order: _OrderKACF = ...,
25052506
casting: _CastingKind = ...,
25062507
subok: bool = ...,
2507-
copy: bool = ...,
2508+
copy: bool | _CopyMode = ...,
25082509
) -> Any: ...
25092510

2510< F438 code>2511
# NOTE: `view` will perform a 0D->scalar cast,
@@ -3253,6 +3254,11 @@ trunc: _UFunc_Nin1_Nout1[L['trunc'], L[7], None]
32533254

32543255
abs = absolute
32553256

3257+
class _CopyMode(enum.Enum):
3258+
ALWAYS: L[True]
3259+
IF_NEEDED: L[False]
3260+
NEVER: L[2]
3261+
32563262
# Warnings
32573263
class ModuleDeprecationWarning(DeprecationWarning): ...
32583264
class VisibleDeprecationWarning(UserWarning): ...

numpy/_globals.py

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,11 @@ def foo(arg=np._NoValue):
1515
motivated this module.
1616
1717
"""
18+
import enum
19+
1820
__ALL__ = [
19-
'ModuleDeprecationWarning', 'VisibleDeprecationWarning', '_NoValue'
21+
'ModuleDeprecationWarning', 'VisibleDeprecationWarning',
22+
'_NoValue', '_CopyMode'
2023
]
2124

2225

@@ -89,3 +92,38 @@ def __repr__(self):
8992

9093

9194
_NoValue = _NoValueType()
95+
96+
97+
class _CopyMode(enum.Enum):
98+
"""
99+
An enumeration for the copy modes supported
100+
by numpy.copy() and numpy.array(). The following three modes are supported,
101+
102+
- ALWAYS: This means that a deep copy of the input
103+
array will always be taken.
104+
- IF_NEEDED: This means that a deep copy of the input
105+
array will be taken only if necessary.
106+
- NEVER: This means that the deep copy will never be taken.
107+
If a copy cannot be avoided then a `ValueError` will be
108+
raised.
109+
110+
Note that the buffer-protocol could in theory do copies. NumPy currently
111+
assumes an object exporting the buffer protocol will never do this.
112+
"""
113+
114+
ALWAYS = True
115+
IF_NEEDED = False
116+
NEVER = 2
117+
118+
def __bool__(self):
119+
# For backwards compatiblity
120+
if self == _CopyMode.ALWAYS:
121+
return True
122+
123+
if self == _CopyMode.IF_NEEDED:
124+
return False
125+
126+
raise ValueError(f"{self} is neither True nor False.")
127+
128+
129+
_CopyMode.__module__ = 'numpy'

numpy/array_api/_creation_functions.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ def asarray(
4141
*,
4242
dtype: Optional[Dtype] = None,
4343
device: Optional[Device] = None,
44-
copy: Optional[bool] = None,
44+
copy: Optional[Union[bool, np._CopyMode]] = None,
4545
) -> Array:
4646
"""
4747
Array API compatible wrapper for :py:func:`np.asarray <numpy.asarray>`.
@@ -55,13 +55,13 @@ def asarray(
5555
_check_valid_dtype(dtype)
5656
if device not in ["cpu", None]:
5757
raise ValueError(f"Unsupported device {device!r}")
58-
if copy is False:
58+
if copy in (False, np._CopyMode.IF_NEEDED):
5959
# Note: copy=False is not yet implemented in np.asarray
6060
raise NotImplementedError("copy=False is not yet implemented")
6161
if isinstance(obj, Array):
6262
if dtype is not None and obj.dtype != dtype:
6363
copy = True
64-
if copy is True:
64+
if copy in (True, np._CopyMode.ALWAYS):
6565
return Array._new(np.array(obj._array, copy=True, dtype=dtype))
6666
return obj
6767
if dtype is None and isinstance(obj, int) and (obj > 2 ** 64 or obj < -(2 ** 63)):

numpy/array_api/tests/test_creation_functions.py

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -43,12 +43,18 @@ def test_asarray_copy():
4343
a[0] = 0
4444
assert all(b[0] == 1)
4545
assert all(a[0] == 0)
46-
# Once copy=False is implemented, replace this with
47-
# a = asarray([1])
48-
# b = asarray(a, copy=False)
49-
# a[0] = 0
50-
# assert all(b[0] == 0)
46+
a = asarray([1])
47+
b = asarray(a, copy=np._CopyMode.ALWAYS)
48+
a[0] = 0
49+
assert all(b[0] == 1)
50+
assert all(a[0] == 0)
51+
a = asarray([1])
52+
b = asarray(a, copy=np._CopyMode.NEVER)
53+
a[0] = 0
54+
assert all(b[0] == 0)
5155
assert_raises(NotImplementedError, lambda: asarray(a, copy=False))
56+
assert_raises(NotImplementedError,
57+
lambda: asarray(a, copy=np._CopyMode.IF_NEEDED))
5258

5359

5460
def test_arange_errors():

numpy/core/include/numpy/ndarraytypes.h

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -868,7 +868,7 @@ typedef int (PyArray_FinalizeFunc)(PyArrayObject *, PyObject *);
868868

869869
/*
870870
* Always copy the array. Returned arrays are always CONTIGUOUS,
871-
* ALIGNED, and WRITEABLE.
871+
* ALIGNED, and WRITEABLE. See also: NPY_ARRAY_ENSURENOCOPY = 0x4000.
872872
*
873873
* This flag may be requested in constructor functions.
874874
*/
@@ -937,6 +937,13 @@ typedef int (PyArray_FinalizeFunc)(PyArrayObject *, PyObject *);
937937
#define NPY_ARRAY_UPDATEIFCOPY 0x1000 /* Deprecated in 1.14 */
938938
#define NPY_ARRAY_WRITEBACKIFCOPY 0x2000
939939

940+
/*
941+
* No copy may be made while converting from an object/array (result is a view)
942+
*
943+
* This flag may be requested in constructor functions.
944+
*/
945+
#define NPY_ARRAY_ENSURENOCOPY 0x4000
946+
940947
/*
941948
* NOTE: there are also internal flags defined in multiarray/arrayobject.h,
942949
* which start at bit 31 and work down.

numpy/core/multiarray.pyi

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ from numpy import (
5050
_ModeKind,
5151
_SupportsBuffer,
5252
_IOProtocol,
53+
_CopyMode,
5354
_NDIterFlagsKind,
5455
_NDIterOpFlagsKind,
5556
)
@@ -177,7 +178,7 @@ def array(
177178
object: _ArrayType,
178179
dtype: None = ...,
179180
*,
180-
copy: bool = ...,
181+
copy: bool | _CopyMode = ...,
181182
order: _OrderKACF = ...,
182183
subok: L[True],
183184
ndmin: int = ...,
@@ -188,7 +189,7 @@ def array(
188189
object: _ArrayLike[_SCT],
189190
dtype: None = ...,
190191
*,
191-
copy: bool = ...,
192+
copy: bool | _CopyMode = ...,
192193
order: _OrderKACF = ...,
193194
subok: bool = ...,
194195
ndmin: int = ...,
@@ -199,7 +200,7 @@ def array(
199200
object: object,
200201
dtype: None = ...,
201202
*,
202-
copy: bool = ...,
203+
copy: bool | _CopyMode = ...,
203204
order: _OrderKACF = ...,
204205
subok: bool = ...,
205206
ndmin: int = ...,
@@ -210,7 +211,7 @@ def array(
210211
object: Any,
211212
dtype: _DTypeLike[_SCT],
212213
*,
213-
copy: bool = ...,
214+
copy: bool | _CopyMode = ...,
214215
order: _OrderKACF = ...,
215216
subok: bool = ...,
216217
ndmin: int = ...,
@@ -221,7 +222,7 @@ def array(
221222
object: Any,
222223
dtype: DTypeLike,
223224
*,
224-
copy: bool = ...,
225+
copy: bool | _CopyMode = ...,
225226
order: _OrderKACF = ...,
226227
subok: bool = ...,
227228
ndmin: int = ...,

numpy/core/src/multiarray/_multiarray_tests.c.src

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2363,6 +2363,17 @@ run_intp_converter(PyObject* NPY_UNUSED(self), PyObject *args)
23632363
return tup;
23642364
}
23652365

2366+
/* used to test NPY_ARRAY_ENSURENOCOPY raises ValueError */
2367+
static PyObject*
2368+
npy_ensurenocopy(PyObject* NPY_UNUSED(self), PyObject* args)
2369+
{
2370+
int flags = NPY_ARRAY_ENSURENOCOPY;
2371+
if (!PyArray_CheckFromAny(args, NULL, 0, 0, flags, NULL)) {
2372+
return NULL;
2373+
}
2374+
Py_RETURN_NONE;
2375+
}
2376+
23662377
static PyMethodDef Multiarray_TestsMethods[] = {
23672378
{"argparse_example_function",
23682379
(PyCFunction)argparse_example_function,
@@ -2424,6 +2435,9 @@ static PyMethodDef Multiarray_TestsMethods[] = {
24242435
{"npy_discard",
24252436
npy_discard,
24262437
METH_O, NULL},
2438+
{"npy_ensurenocopy",
2439+
npy_ensurenocopy,
2440+
METH_O, NULL},
24272441
{"get_buffer_info",
24282442
get_buffer_info,
24292443
METH_VARARGS, NULL},

numpy/core/src/multiarray/array_coercion.c

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -858,6 +858,7 @@ PyArray_AdaptDescriptorToArray(PyArrayObject *arr, PyObject *dtype)
858858
* (Initially it is a pointer to the user-provided head pointer).
859859
* @param fixed_DType User provided fixed DType class
860860
* @param flags Discovery flags (reporting and behaviour flags, see def.)
861+
* @param never_copy Specifies if a copy is allowed during array creation.
861862
* @return The updated number of maximum dimensions (i.e. scalars will set
862863
* this to the current dimensions).
863864
*/
@@ -866,7 +867,8 @@ PyArray_DiscoverDTypeAndShape_Recursive(
866867
PyObject *obj, int curr_dims, int max_dims, PyArray_Descr**out_descr,
867868
npy_intp out_shape[NPY_MAXDIMS],
868869
coercion_cache_obj ***coercion_cache_tail_ptr,
869-
PyArray_DTypeMeta *fixed_DType, enum _dtype_discovery_flags *flags)
870+
PyArray_DTypeMeta *fixed_DType, enum _dtype_discovery_flags *flags,
871+
int never_copy)
870872
{
871873
PyArrayObject *arr = NULL;
872874
PyObject *seq;
@@ -924,7 +926,7 @@ PyArray_DiscoverDTypeAndShape_Recursive(
924926
requested_descr = *out_descr;
925927
}
926928
arr = (PyArrayObject *)_array_from_array_like(obj,
927-
requested_descr, 0, NULL);
929+
requested_descr, 0, NULL, never_copy);
928930
if (arr == NULL) {
929931
return -1;
930932
}
@@ -1118,7 +1120,7 @@ PyArray_DiscoverDTypeAndShape_Recursive(
11181120
max_dims = PyArray_DiscoverDTypeAndShape_Recursive(
11191121
objects[i], curr_dims + 1, max_dims,
11201122
out_descr, out_shape, coercion_cache_tail_ptr, fixed_DType,
1121-
flags);
1123+
flags, never_copy);
11221124

11231125
if (max_dims < 0) {
11241126
return -1;
@@ -1158,6 +1160,7 @@ PyArray_DiscoverDTypeAndShape_Recursive(
11581160
* The result may be unchanged (remain NULL) when converting a
11591161
* sequence with no elements. In this case it is callers responsibility
11601162
* to choose a default.
1163+
* @param never_copy Specifies that a copy is not allowed.
11611164
* @return dimensions of the discovered object or -1 on error.
11621165
* WARNING: If (and only if) the output is a single array, the ndim
11631166
* returned _can_ exceed the maximum allowed number of dimensions.
@@ -1170,7 +1173,7 @@ PyArray_DiscoverDTypeAndShape(
11701173
npy_intp out_shape[NPY_MAXDIMS],
11711174
coercion_cache_obj **coercion_cache,
11721175
PyArray_DTypeMeta *fixed_DType, PyArray_Descr *requested_descr,
1173-
PyArray_Descr **out_descr)
1176+
PyArray_Descr **out_descr, int never_copy)
11741177
{
11751178
coercion_cache_obj **coercion_cache_head = coercion_cache;
11761179
*coercion_cache = NULL;
@@ -1215,7 +1218,7 @@ PyArray_DiscoverDTypeAndShape(
12151218

12161219
int ndim = PyArray_DiscoverDTypeAndShape_Recursive(
12171220
obj, 0, max_dims, out_descr, out_shape, &coercion_cache,
1218-
fixed_DType, &flags);
1221+
fixed_DType, &flags, never_copy);
12191222
if (ndim < 0) {
12201223
goto fail;
12211224
}
@@ -1500,7 +1503,7 @@ _discover_array_parameters(PyObject *NPY_UNUSED(self),
15001503
int ndim = PyArray_DiscoverDTypeAndShape(
15011504
obj, NPY_MAXDIMS, shape,
15021505
&coercion_cache,
1503-
fixed_DType, fixed_descriptor, (PyArray_Descr **)&out_dtype);
1506+
fixed_DType, fixed_descriptor, (PyArray_Descr **)&out_dtype, 0);
15041507
Py_XDECREF(fixed_DType);
15051508
Py_XDECREF(fixed_descriptor);
15061509
if (ndim < 0) {

numpy/core/src/multiarray/array_coercion.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ PyArray_DiscoverDTypeAndShape(
3131
npy_intp out_shape[NPY_MAXDIMS],
3232
coercion_cache_obj **coercion_cache,
3333
PyArray_DTypeMeta *fixed_DType, PyArray_Descr *requested_descr,
34-
PyArray_Descr **out_descr);
34+
PyArray_Descr **out_descr, int never_copy);
3535

3636
NPY_NO_EXPORT int
3737
PyArray_ExtractDTypeAndDescriptor(PyObject *dtype,

0 commit comments

Comments
 (0)
0