Skip to content

Commit 6e8f6ca

Browse files
authored
Merge pull request numpy#26245 from mtsokol/copy-kw-docs-update
DOC: Update `__array__` `copy` keyword docs
2 parents 4d65246 + bff75cf commit 6e8f6ca

File tree

5 files changed

+45
-19
lines changed

5 files changed

+45
-19
lines changed

doc/source/numpy_2_0_migration_guide.rst

Lines changed: 17 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -412,20 +412,23 @@ The :ref:`copy keyword behavior changes <copy-keyword-changes-2.0>` in
412412
`~numpy.asarray`, `~numpy.array` and `ndarray.__array__
413413
<numpy.ndarray.__array__>` may require these changes:
414414

415-
1. Code using ``np.array(..., copy=False)`` can in most cases be changed to
416-
``np.asarray(...)``. Older code tended to use ``np.array`` like this because
417-
it had less overhead than the default ``np.asarray`` copy-if-needed
418-
behavior. This is no longer true, and ``np.asarray`` is the preferred function.
419-
2. For code that explicitly needs to pass ``None``/``False`` meaning "copy if
420-
needed" in a way that's compatible with NumPy 1.x and 2.x, see
421-
`scipy#20172 <https://github.com/scipy/scipy/pull/20172>`__ for an example
422-
of how to do so.
423-
3. For any ``__array__`` method on a non-NumPy array-like object, a
424-
``copy=None`` keyword can be added to the signature - this will work with
425-
older NumPy versions as well. If ``copy`` keyword is considered in
426-
the ``__array__`` method implementation, then for ``copy=True`` always
427-
return a new copy.
428-
415+
* Code using ``np.array(..., copy=False)`` can in most cases be changed to
416+
``np.asarray(...)``. Older code tended to use ``np.array`` like this because
417+
it had less overhead than the default ``np.asarray`` copy-if-needed
418+
behavior. This is no longer true, and ``np.asarray`` is the preferred function.
419+
* For code that explicitly needs to pass ``None``/``False`` meaning "copy if
420+
needed" in a way that's compatible with NumPy 1.x and 2.x, see
421+
`scipy#20172 <https://github.com/scipy/scipy/pull/20172>`__ for an example
422+
of how to do so.
423+
* For any ``__array__`` method on a non-NumPy array-like object, ``dtype=None``
424+
and ``copy=None`` keywords must be added to the signature - this will work with older
425+
NumPy versions as well (although older numpy versions will never pass in ``copy`` keyword).
426+
If the keywords are added to the ``__array__`` signature, then for:
427+
428+
* ``copy=True`` and any ``dtype`` value always return a new copy,
429+
* ``copy=None`` create a copy if required (for example by ``dtype``),
430+
* ``copy=False`` a copy must never be made. If a copy is needed to return a numpy array
431+
or satisfy ``dtype``, then raise an exception (``ValueError``).
429432

430433
Writing numpy-version-dependent code
431434
------------------------------------

doc/source/user/basics.dispatch.rst

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,10 @@ example that has rather narrow utility but illustrates the concepts involved.
2323
... def __repr__(self):
2424
... return f"{self.__class__.__name__}(N={self._N}, value={self._i})"
2525
... def __array__(self, dtype=None, copy=None):
26+
... if copy is False:
27+
... raise ValueError(
28+
... "`copy=False` isn't supported. A copy is always created."
29+
... )
2630
... return self._i * np.eye(self._N, dtype=dtype)
2731

2832
Our custom array can be instantiated like:
@@ -85,6 +89,10 @@ For this example we will only handle the method ``__call__``
8589
... def __repr__(self):
8690
... return f"{self.__class__.__name__}(N={self._N}, value={self._i})"
8791
... def __array__(self, dtype=None, copy=None):
92+
... if copy is False:
93+
... raise ValueError(
94+
... "`copy=False` isn't supported. A copy is always created."
95+
... )
8896
... return self._i * np.eye(self._N, dtype=dtype)
8997
... def __array_ufunc__(self, ufunc, method, *inputs, **kwargs):
9098
... if method == '__call__':
@@ -136,6 +144,10 @@ conveniently by inheriting from the mixin
136144
... def __repr__(self):
137145
... return f"{self.__class__.__name__}(N={self._N}, value={self._i})"
138146
... def __array__(self, dtype=None, copy=None):
147+
... if copy is False:
148+
... raise ValueError(
149+
... "`copy=False` isn't supported. A copy is always created."
150+
... )
139151
... return self._i * np.eye(self._N, dtype=dtype)
140152
... def __array_ufunc__(self, ufunc, method, *inputs, **kwargs):
141153
... if method == '__call__':
@@ -174,6 +186,10 @@ functions to our custom variants.
174186
... def __repr__(self):
175187
... return f"{self.__class__.__name__}(N={self._N}, value={self._i})"
176188
... def __array__(self, dtype=None, copy=None):
189+
... if copy is False:
190+
... raise ValueError(
191+
... "`copy=False` isn't supported. A copy is always created."
192+
... )
177193
... return self._i * np.eye(self._N, dtype=dtype)
178194
... def __array_ufunc__(self, ufunc, method, *inputs, **kwargs):
179195
... if method == '__call__':

doc/source/user/basics.interoperability.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,8 @@ We can check that ``arr`` and ``new_arr`` share the same data buffer:
113113
array([1000, 2, 3, 4])
114114

115115

116+
.. _dunder_array.interface:
117+
116118
The ``__array__()`` method
117119
~~~~~~~~~~~~~~~~~~~~~~~~~~
118120

numpy/_core/_add_newdocs.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2945,16 +2945,20 @@
29452945

29462946
add_newdoc('numpy._core.multiarray', 'ndarray', ('__array__',
29472947
"""
2948-
a.__array__([dtype], /, *, copy=None)
2948+
a.__array__([dtype], *, copy=None)
29492949
2950-
For ``dtype`` parameter it returns either a new reference to self if
2951-
``dtype`` is not given or a new array of provided data type if ``dtype``
2950+
For ``dtype`` parameter it returns a new reference to self if
2951+
``dtype`` is not given or it matches array's data type.
2952+
A new array of provided data type is returned if ``dtype``
29522953
is different from the current data type of the array.
29532954
For ``copy`` parameter it returns a new reference to self if
29542955
``copy=False`` or ``copy=None`` and copying isn't enforced by ``dtype``
29552956
parameter. The method returns a new array for ``copy=True``, regardless of
29562957
``dtype`` parameter.
29572958
2959+
A more detailed explanation of the ``__array__`` interface
2960+
can be found in :ref:`dunder_array.interface`.
2961+
29582962
"""))
29592963

29602964

numpy/_core/src/multiarray/ctors.c

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2459,8 +2459,9 @@ check_or_clear_and_warn_error_if_due_to_copy_kwarg(PyObject *kwnames)
24592459
Py_DECREF(type);
24602460
Py_DECREF(value);
24612461
Py_XDECREF(traceback);
2462-
if (DEPRECATE("__array__ should implement the 'dtype' and "
2463-
"'copy' keyword argument") < 0) {
2462+
if (DEPRECATE("__array__ implementation doesn't accept a copy keyword, "
2463+
"so passing copy=False failed. __array__ must implement "
2464+
"'dtype' and 'copy' keyword arguments.") < 0) {
24642465
return -1;
24652466
}
24662467
return 0;

0 commit comments

Comments
 (0)