Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

API: Introducecopy argument fornp.asarray [Array API]#25168

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to ourterms of service andprivacy statement. We’ll occasionally send you account related emails.

Already on GitHub?Sign in to your account

Merged
ngoldbaum merged 5 commits intonumpy:mainfrommtsokol:asarray-copy-arg
Feb 29, 2024
Merged
Show file tree
Hide file tree
Changes from1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
PrevPrevious commit
np.array: call __array__ with copy keyword
  • Loading branch information
@mtsokol
mtsokol committedFeb 29, 2024
commitbeb523cf5cd1d939b3dbdedef1565ab18f07a5c0
37 changes: 24 additions & 13 deletionsdoc/source/reference/arrays.classes.rst
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -305,19 +305,30 @@ NumPy provides several hooks that classes can customize:
.. note:: For ufuncs, it is hoped to eventually deprecate this method in
favour of :func:`__array_ufunc__`.

.. py:method:: class.__array__([dtype])

If defined on an object, should return an ``ndarray``.
This method is called by array-coercion functions like np.array()
if an object implementing this interface is passed to those functions.
Please refer to :ref:`Interoperability with NumPy <basics.interoperability>`
for the protocol hierarchy, of which ``__array__`` is the oldest and least
desirable.

.. note:: If a class (ndarray subclass or not) having the :func:`__array__`
method is used as the output object of an :ref:`ufunc
<ufuncs-output-type>`, results will *not* be written to the object
returned by :func:`__array__`. This practice will return ``TypeError``.
.. py:method:: class.__array__(dtype=None, copy=None)

If defined on an object, should return an ``ndarray``.
This method is called by array-coercion functions like np.array()
if an object implementing this interface is passed to those functions.
The third-party implementations of ``__array__`` must take ``dtype`` and
``copy`` keyword arguments, as ignoring them might break third-party code
or NumPy itself.

- ``dtype`` is a data type of the returned array.
- ``copy`` is an optional boolean that indicates whether a copy should be
returned. For ``True`` a copy should always be made, for ``None`` only
if required (e.g. due to passed ``dtype`` value), and for ``False`` a copy
should never be made (if a copy is still required, an appropriate exception
should be raised).

Please refer to :ref:`Interoperability with NumPy <basics.interoperability>`
for the protocol hierarchy, of which ``__array__`` is the oldest and least
desirable.

.. note:: If a class (ndarray subclass or not) having the :func:`__array__`
method is used as the output object of an :ref:`ufunc
<ufuncs-output-type>`, results will *not* be written to the object
returned by :func:`__array__`. This practice will return ``TypeError``.

.. _matrix-objects:

Expand Down
5 changes: 3 additions & 2 deletionsdoc/source/reference/c-api/array.rst
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -514,8 +514,9 @@ From other objects
PyObject* op, PyArray_Descr* dtype, PyObject* context)

Return an ndarray object from a Python object that exposes the
:obj:`~numpy.class.__array__` method. The :obj:`~numpy.class.__array__`
method can take 0, or 1 argument ``([dtype])``. ``context`` is unused.
:obj:`~numpy.class.__array__` method. The third-party implementations of
:obj:`~numpy.class.__array__` must take ``dtype`` and ``copy`` keyword
arguments. ``context`` is unused.

.. c:function:: PyObject* PyArray_ContiguousFromAny( \
PyObject* op, int typenum, int min_depth, int max_depth)
Expand Down
9 changes: 4 additions & 5 deletionsdoc/source/user/basics.dispatch.rst
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -22,7 +22,7 @@ example that has rather narrow utility but illustrates the concepts involved.
... self._i = value
... def __repr__(self):
... return f"{self.__class__.__name__}(N={self._N}, value={self._i})"
... def __array__(self, dtype=None):
... def __array__(self, dtype=None, copy=None):
... return self._i * np.eye(self._N, dtype=dtype)

Our custom array can be instantiated like:
Expand DownExpand Up@@ -84,7 +84,7 @@ For this example we will only handle the method ``__call__``
... self._i = value
... def __repr__(self):
... return f"{self.__class__.__name__}(N={self._N}, value={self._i})"
... def __array__(self, dtype=None):
... def __array__(self, dtype=None, copy=None):
... return self._i * np.eye(self._N, dtype=dtype)
... def __array_ufunc__(self, ufunc, method, *inputs, **kwargs):
... if method == '__call__':
Expand DownExpand Up@@ -135,7 +135,7 @@ conveniently by inheriting from the mixin
... self._i = value
... def __repr__(self):
... return f"{self.__class__.__name__}(N={self._N}, value={self._i})"
... def __array__(self, dtype=None):
... def __array__(self, dtype=None, copy=None):
... return self._i * np.eye(self._N, dtype=dtype)
... def __array_ufunc__(self, ufunc, method, *inputs, **kwargs):
... if method == '__call__':
Expand DownExpand Up@@ -173,7 +173,7 @@ functions to our custom variants.
... self._i = value
... def __repr__(self):
... return f"{self.__class__.__name__}(N={self._N}, value={self._i})"
... def __array__(self, dtype=None):
... def __array__(self, dtype=None, copy=None):
... return self._i * np.eye(self._N, dtype=dtype)
... def __array_ufunc__(self, ufunc, method, *inputs, **kwargs):
... if method == '__call__':
Expand DownExpand Up@@ -306,4 +306,3 @@ Refer to the `dask source code <https://github.com/dask/dask>`_ and
examples of custom array containers.

See also :doc:`NEP 18<neps:nep-0018-array-function-protocol>`.

14 changes: 12 additions & 2 deletionsdoc/source/user/basics.interoperability.rst
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -75,8 +75,8 @@ relies on the existence of the following attributes or methods:
- ``__array_interface__``: a Python dictionary containing the shape, the
element type, and optionally, the data buffer address and the strides of an
array-like object;
- ``__array__()``: a method returning the NumPy ndarray view of an array-like
object;
- ``__array__()``: a method returning the NumPy ndarraycopy or aview of an
array-likeobject;

The ``__array_interface__`` attribute can be inspected directly:

Expand DownExpand Up@@ -125,6 +125,16 @@ new ndarray object. This is not optimal, as coercing arrays into ndarrays may
cause performance problems or create the need for copies and loss of metadata,
as the original object and any attributes/behavior it may have had, is lost.

The signature of the method should be ``__array__(self, dtype=None, copy=None)``.
If a passed ``dtype`` isn't ``None`` and different than the object's data type,
a casting should happen to a specified type. If ``copy`` is ``None``, a copy
should be made only if ``dtype`` argument enforces it. For ``copy=True``, a copy
should always be made, where ``copy=False`` should raise an exception if a copy
is needed.

If a class implements the old signature ``__array__(self)``, for ``np.array(a)``
a warning will be raised saying that ``dtype`` and ``copy`` arguments are missing.

To see an example of a custom array implementation including the use of
``__array__()``, see :ref:`basics.dispatch`.

Expand Down
12 changes: 8 additions & 4 deletionsnumpy/__init__.pyi
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -1464,9 +1464,13 @@ class ndarray(_ArrayOrScalarCommon, Generic[_ShapeType, _DType_co]):
def __class_getitem__(self, item: Any) -> GenericAlias: ...

@overload
def __array__(self, dtype: None = ..., /) -> ndarray[Any, _DType_co]: ...
def __array__(
self, dtype: None = ..., /, *, copy: None | bool = ...
) -> ndarray[Any, _DType_co]: ...
@overload
def __array__(self, dtype: _DType, /) -> ndarray[Any, _DType]: ...
def __array__(
self, dtype: _DType, /, *, copy: None | bool = ...
) -> ndarray[Any, _DType]: ...

def __array_ufunc__(
self,
Expand DownExpand Up@@ -3715,9 +3719,9 @@ class poly1d:
__hash__: ClassVar[None] # type: ignore

@overload
def __array__(self, t: None = ...) -> NDArray[Any]: ...
def __array__(self, t: None = ..., copy: None | bool = ...) -> NDArray[Any]: ...
@overload
def __array__(self, t: _DType) -> ndarray[Any, _DType]: ...
def __array__(self, t: _DType, copy: None | bool = ...) -> ndarray[Any, _DType]: ...

@overload
def __call__(self, val: _ScalarLike_co) -> Any: ...
Expand Down
2 changes: 1 addition & 1 deletionnumpy/_core/defchararray.py
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -1148,7 +1148,7 @@ class adds the following functionality:

copy : bool, optional
If true (default), then the object is copied. Otherwise, a copy
will only be made if __array__ returns a copy, if obj is a
will only be made if``__array__`` returns a copy, if obj is a
nested sequence, or if a copy is needed to satisfy any of the other
requirements (`itemsize`, unicode, `order`, etc.).

Expand Down
56 changes: 29 additions & 27 deletionsnumpy/_core/src/multiarray/ctors.c
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -1477,16 +1477,7 @@ _array_from_array_like(PyObject *op,
}
}

/*
* If op supplies the __array__ function.
* The documentation says this should produce a copy, so
* we skip this method if writeable is true, because the intent
* of writeable is to modify the operand.
* XXX: If the implementation is wrong, and/or if actual
* usage requires this behave differently,
* this should be changed!
*/
if (!writeable && tmp == Py_NotImplemented) {
if (tmp == Py_NotImplemented) {
tmp = PyArray_FromArrayAttr_int(op, requested_dtype, never_copy);
if (tmp == NULL) {
return NULL;
Expand DownExpand Up@@ -2418,9 +2409,8 @@ PyArray_FromInterface(PyObject *origin)
* @param descr The desired `arr.dtype`, passed into the `__array__` call,
* as information but is not checked/enforced!
* @param never_copy Specifies that a copy is not allowed.
* NOTE: Currently, this means an error is raised instead of calling
* `op.__array__()`. In the future we could call for example call
* `op.__array__(never_copy=True)` instead.
* NOTE: For false it passes `op.__array__(copy=None)`,
* for true: `op.__array__(copy=False)`.
* @returns NotImplemented if `__array__` is not defined or a NumPy array
* (or subclass). On error, return NULL.
*/
Expand All@@ -2438,15 +2428,6 @@ PyArray_FromArrayAttr_int(
}
return Py_NotImplemented;
}
if (never_copy) {
/* Currently, we must always assume that `__array__` returns a copy */
PyErr_SetString(PyExc_ValueError,
"Unable to avoid copy while converting from an object "
"implementing the `__array__` protocol. NumPy cannot ensure "
"that no copy will be made.");
Py_DECREF(array_meth);
return NULL;
}

if (PyType_Check(op) && PyObject_HasAttrString(array_meth, "__get__")) {
/*
Expand All@@ -2458,12 +2439,33 @@ PyArray_FromArrayAttr_int(
Py_DECREF(array_meth);
return Py_NotImplemented;
}
if (descr == NULL) {
new = PyObject_CallFunction(array_meth, NULL);
}
else {
new = PyObject_CallFunction(array_meth, "O", descr);

PyObject *copy = never_copy ? Py_False : Py_None;
PyObject *kwargs = PyDict_New();
PyDict_SetItemString(kwargs, "copy", copy);
PyObject *args = descr != NULL ? PyTuple_Pack(1, descr) : PyTuple_New(0);

new = PyObject_Call(array_meth, args, kwargs);

if (PyErr_Occurred()) {
PyObject *type, *value, *traceback;
PyErr_Fetch(&type, &value, &traceback);
if (PyUnicode_Check(value) && PyUnicode_CompareWithASCIIString(value,
"__array__() got an unexpected keyword argument 'copy'") == 0) {
Py_DECREF(type);
Py_XDECREF(value);
Py_XDECREF(traceback);
if (PyErr_WarnEx(PyExc_UserWarning,
"__array__ should implement 'dtype' and 'copy' keywords", 1) < 0) {
return NULL;
}
Py_SETREF(new, PyObject_Call(array_meth, args, NULL));
} else {
PyErr_Restore(type, value, traceback);
return NULL;
}
}

Py_DECREF(array_meth);
if (new == NULL) {
return NULL;
Expand Down
6 changes: 3 additions & 3 deletionsnumpy/_core/src/multiarray/iterators.c
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -1071,7 +1071,7 @@ static PyMappingMethods iter_as_mapping = {
* ignored.
*/
static PyArrayObject *
iter_array(PyArrayIterObject *it, PyObject *NPY_UNUSED(op))
iter_array(PyArrayIterObject *it, PyObject *NPY_UNUSED(args), PyObject *NPY_UNUSED(kwds))
{

PyArrayObject *ret;
Expand DownExpand Up@@ -1120,7 +1120,7 @@ static PyMethodDef iter_methods[] = {
/* to get array */
{"__array__",
(PyCFunction)iter_array,
METH_VARARGS, NULL},
METH_VARARGS | METH_KEYWORDS, NULL},
{"copy",
(PyCFunction)iter_copy,
METH_VARARGS, NULL},
Expand All@@ -1132,7 +1132,7 @@ iter_richcompare(PyArrayIterObject *self, PyObject *other, int cmp_op)
{
PyArrayObject *new;
PyObject *ret;
new = (PyArrayObject *)iter_array(self, NULL);
new = (PyArrayObject *)iter_array(self, NULL, NULL);
if (new == NULL) {
return NULL;
}
Expand Down
3 changes: 2 additions & 1 deletionnumpy/_core/src/multiarray/methods.c
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -928,7 +928,7 @@ array_getarray(PyArrayObject *self, PyObject *args, PyObject *kwds)
{
PyArray_Descr *newtype = NULL;
NPY_COPYMODE copy = NPY_COPY_IF_NEEDED;
static char *kwlist[] = {"", "copy", NULL};
static char *kwlist[] = {"dtype", "copy", NULL};
PyObject *ret;

if (!PyArg_ParseTupleAndKeywords(args, kwds, "|O&$O&:__array__", kwlist,
Expand DownExpand Up@@ -981,6 +981,7 @@ array_getarray(PyArrayObject *self, PyObject *args, PyObject *kwds)
} else { // copy == NPY_COPY_NEVER
PyErr_SetString(PyExc_ValueError,
"Unable to avoid copy while creating an array from given array.");
Py_DECREF(self);
return NULL;
}
}
Expand Down
2 changes: 1 addition & 1 deletionnumpy/_core/src/multiarray/multiarraymodule.c
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -1655,7 +1655,7 @@ _array_fromobject_generic(
if (copy == NPY_COPY_ALWAYS) {
flags = NPY_ARRAY_ENSURECOPY;
}
else if (copy == NPY_COPY_NEVER) {
else if (copy == NPY_COPY_NEVER) {
flags = NPY_ARRAY_ENSURENOCOPY;
}
if (order == NPY_CORDER) {
Expand Down
6 changes: 4 additions & 2 deletionsnumpy/_core/tests/test_api.py
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -87,8 +87,10 @@ def test_array_array():
assert_equal(bytes(np.array(o).data), bytes(a.data))

# test array
o = type("o", (object,),
dict(__array__=lambda *x: np.array(100.0, dtype=np.float64)))()
def custom__array__(self, dtype=None, copy=None):
return np.array(100.0, dtype=dtype, copy=copy)

o = type("o", (object,), dict(__array__=custom__array__))()
assert_equal(np.array(o, dtype=np.float64), np.array(100.0, np.float64))

# test recursion
Expand Down
6 changes: 3 additions & 3 deletionsnumpy/_core/tests/test_array_coercion.py
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -53,7 +53,7 @@ class ArrayDunder(_SequenceLike):
def __init__(self, a):
self.a = a

def __array__(self, dtype=None):
def __array__(self, dtype=None, copy=None):
return self.a

yield param(ArrayDunder, id="__array__")
Expand DownExpand Up@@ -706,7 +706,7 @@ def __array_interface__(self):
def __array_struct__(self):
pass

def __array__(self):
def __array__(self, dtype=None, copy=None):
pass

arr = np.array(ArrayLike)
Expand DownExpand Up@@ -832,7 +832,7 @@ class TestSpecialAttributeLookupFailure:

class WeirdArrayLike:
@property
def __array__(self):
def __array__(self, dtype=None, copy=None):
raise RuntimeError("oops!")

class WeirdArrayInterface:
Expand Down
2 changes: 1 addition & 1 deletionnumpy/_core/tests/test_deprecations.py
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -696,7 +696,7 @@ class TestDeprecatedArrayWrap(_DeprecationTestCase):

def test_deprecated(self):
class Test1:
def __array__(self,):
def __array__(self, dtype=None, copy=None):
return np.arange(4)

def __array_wrap__(self, arr, context=None):
Expand Down
2 changes: 1 addition & 1 deletionnumpy/_core/tests/test_indexing.py
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -420,7 +420,7 @@ def __index__(self):

class ArrayLike:
# Simple array, should behave like the array
def __array__(self):
def __array__(self, dtype=None, copy=None):
return np.array(0)

a = np.zeros(())
Expand Down
2 changes: 1 addition & 1 deletionnumpy/_core/tests/test_mem_overlap.py
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -553,7 +553,7 @@ class MyArray2:
def __init__(self, data):
self.data = data

def __array__(self):
def __array__(self, dtype=None, copy=None):
return self.data

for cls in [MyArray, MyArray2]:
Expand Down
Loading

[8]ページ先頭

©2009-2025 Movatter.jp