summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--doc/neps/nep-0037-array-module.rst80
-rw-r--r--numpy/core/src/multiarray/descriptor.c95
2 files changed, 78 insertions, 97 deletions
diff --git a/doc/neps/nep-0037-array-module.rst b/doc/neps/nep-0037-array-module.rst
index 9c89e26ec..387356490 100644
--- a/doc/neps/nep-0037-array-module.rst
+++ b/doc/neps/nep-0037-array-module.rst
@@ -12,7 +12,7 @@ NEP 37 — A dispatch protocol for NumPy-like modules
Abstract
--------
-NEP-18's `__array_function__` has been a mixed success. Some projects (e.g.,
+NEP-18's ``__array_function__`` has been a mixed success. Some projects (e.g.,
dask, CuPy, xarray, sparse, Pint) have enthusiastically adopted it. Others
(e.g., PyTorch, JAX, SciPy) have been more reluctant. Here we propose a new
protocol, ``__array_module__``, that we expect could eventually subsume most
@@ -29,47 +29,47 @@ There are two broad ways in which NEP-18 has fallen short of its goals:
1. **Maintainability concerns**. `__array_function__` has significant
implications for libraries that use it:
- - Projects like `PyTorch
- <https://github.com/pytorch/pytorch/issues/22402>`_, `JAX
- <https://github.com/google/jax/issues/1565>`_ and even `scipy.sparse
- <https://github.com/scipy/scipy/issues/10362>`_ have been reluctant to
- implement `__array_function__` in part because they are concerned about
- **breaking existing code**: users expect NumPy functions like
- ``np.concatenate`` to return NumPy arrays. This is a fundamental
- limitation of the ``__array_function__`` design, which we chose to allow
- overriding the existing ``numpy`` namespace.
- - ``__array_function__`` currently requires an "all or nothing" approach to
- implementing NumPy's API. There is no good pathway for **incremental
- adoption**, which is particularly problematic for established projects
- for which adopting ``__array_function__`` would result in breaking
- changes.
- - It is no longer possible to use **aliases to NumPy functions** within
- modules that support overrides. For example, both CuPy and JAX set
- ``result_type = np.result_type``.
- - Implementing **fall-back mechanisms** for unimplemented NumPy functions
- by using NumPy's implementation is hard to get right (but see the
- `version from dask <https://github.com/dask/dask/pull/5043>`_), because
- ``__array_function__`` does not present a consistent interface.
- Converting all arguments of array type requires recursing into generic
- arguments of the form ``*args, **kwargs``.
+ - Projects like `PyTorch
+ <https://github.com/pytorch/pytorch/issues/22402>`_, `JAX
+ <https://github.com/google/jax/issues/1565>`_ and even `scipy.sparse
+ <https://github.com/scipy/scipy/issues/10362>`_ have been reluctant to
+ implement `__array_function__` in part because they are concerned about
+ **breaking existing code**: users expect NumPy functions like
+ ``np.concatenate`` to return NumPy arrays. This is a fundamental
+ limitation of the ``__array_function__`` design, which we chose to allow
+ overriding the existing ``numpy`` namespace.
+ - ``__array_function__`` currently requires an "all or nothing" approach to
+ implementing NumPy's API. There is no good pathway for **incremental
+ adoption**, which is particularly problematic for established projects
+ for which adopting ``__array_function__`` would result in breaking
+ changes.
+ - It is no longer possible to use **aliases to NumPy functions** within
+ modules that support overrides. For example, both CuPy and JAX set
+ ``result_type = np.result_type``.
+ - Implementing **fall-back mechanisms** for unimplemented NumPy functions
+ by using NumPy's implementation is hard to get right (but see the
+ `version from dask <https://github.com/dask/dask/pull/5043>`_), because
+ ``__array_function__`` does not present a consistent interface.
+ Converting all arguments of array type requires recursing into generic
+ arguments of the form ``*args, **kwargs``.
2. **Limitations on what can be overridden.** ``__array_function__`` has some
important gaps, most notably array creation and coercion functions:
- - **Array creation** routines (e.g., ``np.arange`` and those in
- ``np.random``) need some other mechanism for indicating what type of
- arrays to create. `NEP 36 <https://github.com/numpy/numpy/pull/14715>`_
- proposed adding optional ``like=`` arguments to functions without
- existing array arguments. However, we still lack any mechanism to
- override methods on objects, such as those needed by
- ``np.random.RandomState``.
- - **Array conversion** can't reuse the existing coercion functions like
- ``np.asarray``, because ``np.asarray`` sometimes means "convert to an
- exact ``np.ndarray``" and other times means "convert to something _like_
- a NumPy array." This led to the `NEP 30
- <https://numpy.org/neps/nep-0030-duck-array-protocol.html>`_ proposal for
- a separate ``np.duckarray`` function, but this still does not resolve how
- to cast one duck array into a type matching another duck array.
+ - **Array creation** routines (e.g., ``np.arange`` and those in
+ ``np.random``) need some other mechanism for indicating what type of
+ arrays to create. `NEP 36 <https://github.com/numpy/numpy/pull/14715>`_
+ proposed adding optional ``like=`` arguments to functions without
+ existing array arguments. However, we still lack any mechanism to
+ override methods on objects, such as those needed by
+ ``np.random.RandomState``.
+ - **Array conversion** can't reuse the existing coercion functions like
+ ``np.asarray``, because ``np.asarray`` sometimes means "convert to an
+ exact ``np.ndarray``" and other times means "convert to something _like_
+ a NumPy array." This led to the `NEP 30
+ <https://numpy.org/neps/nep-0030-duck-array-protocol.html>`_ proposal for
+ a separate ``np.duckarray`` function, but this still does not resolve how
+ to cast one duck array into a type matching another duck array.
``get_array_module`` and the ``__array_module__`` protocol
----------------------------------------------------------
@@ -196,7 +196,7 @@ general because such fall-back behavior can be error prone):
return getattr(base_module, name, getattr(numpy, name))
Subclassing from ``numpy.ndarray``
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
All of the same guidance about well-defined type casting hierarchies from
NEP-18 still applies. ``numpy.ndarray`` itself contains a matching
@@ -251,7 +251,7 @@ equivalent to this Python code:
return implementing_arrays, types
Relationship with ``__array_ufunc__`` and ``__array_function__``
----------------------------------------------------------------
+----------------------------------------------------------------
These older protocols have distinct use-cases and should remain
===============================================================
diff --git a/numpy/core/src/multiarray/descriptor.c b/numpy/core/src/multiarray/descriptor.c
index 7a94929dd..1f151fd58 100644
--- a/numpy/core/src/multiarray/descriptor.c
+++ b/numpy/core/src/multiarray/descriptor.c
@@ -43,6 +43,24 @@ static PyArray_Descr *
_use_inherit(PyArray_Descr *type, PyObject *newobj, int *errflag);
static PyArray_Descr *
+_arraydescr_run_converter(PyObject *arg, int align)
+{
+ PyArray_Descr *type = NULL;
+ if (align) {
+ if (PyArray_DescrAlignConverter(arg, &type) == NPY_FAIL) {
+ return NULL;
+ }
+ }
+ else {
+ if (PyArray_DescrConverter(arg, &type) == NPY_FAIL) {
+ return NULL;
+ }
+ }
+ assert(type != NULL);
+ return type;
+}
+
+static PyArray_Descr *
_arraydescr_from_ctypes_type(PyTypeObject *type)
{
PyObject *_numpy_dtype_ctypes;
@@ -232,15 +250,9 @@ _convert_from_tuple(PyObject *obj, int align)
if (PyTuple_GET_SIZE(obj) != 2) {
return NULL;
}
- if (align) {
- if (!PyArray_DescrAlignConverter(PyTuple_GET_ITEM(obj, 0), &type)) {
- return NULL;
- }
- }
- else {
- if (!PyArray_DescrConverter(PyTuple_GET_ITEM(obj, 0), &type)) {
- return NULL;
- }
+ type = _arraydescr_run_converter(PyTuple_GET_ITEM(obj, 0), align);
+ if (type == NULL) {
+ return NULL;
}
val = PyTuple_GET_ITEM(obj,1);
/* try to interpret next item as a type */
@@ -411,7 +423,6 @@ static PyArray_Descr *
_convert_from_array_descr(PyObject *obj, int align)
{
int n, i, totalsize;
- int ret;
PyObject *fields, *item, *newobj;
PyObject *name, *tup, *title;
PyObject *nameslist;
@@ -491,31 +502,22 @@ _convert_from_array_descr(PyObject *obj, int align)
/* Process rest */
if (PyTuple_GET_SIZE(item) == 2) {
- if (align) {
- ret = PyArray_DescrAlignConverter(PyTuple_GET_ITEM(item, 1),
- &conv);
- }
- else {
- ret = PyArray_DescrConverter(PyTuple_GET_ITEM(item, 1), &conv);
+ conv = _arraydescr_run_converter(PyTuple_GET_ITEM(item, 1), align);
+ if (conv == NULL) {
+ goto fail;
}
}
else if (PyTuple_GET_SIZE(item) == 3) {
newobj = PyTuple_GetSlice(item, 1, 3);
- if (align) {
- ret = PyArray_DescrAlignConverter(newobj, &conv);
- }
- else {
- ret = PyArray_DescrConverter(newobj, &conv);
- }
+ conv = _arraydescr_run_converter(newobj, align);
Py_DECREF(newobj);
+ if (conv == NULL) {
+ goto fail;
+ }
}
else {
goto fail;
}
- if (ret == NPY_FAIL) {
- goto fail;
- }
-
if ((PyDict_GetItem(fields, name) != NULL)
|| (title
&& PyBaseString_Check(title)
@@ -616,7 +618,6 @@ _convert_from_list(PyObject *obj, int align)
PyArray_Descr *new;
PyObject *key, *tup;
PyObject *nameslist = NULL;
- int ret;
int maxalign = 0;
/* Types with fields need the Python C API for field access */
char dtypeflags = NPY_NEEDS_PYAPI;
@@ -643,13 +644,8 @@ _convert_from_list(PyObject *obj, int align)
for (i = 0; i < n; i++) {
tup = PyTuple_New(2);
key = PyUString_FromFormat("f%d", i);
- if (align) {
- ret = PyArray_DescrAlignConverter(PyList_GET_ITEM(obj, i), &conv);
- }
- else {
- ret = PyArray_DescrConverter(PyList_GET_ITEM(obj, i), &conv);
- }
- if (ret == NPY_FAIL) {
+ conv = _arraydescr_run_converter(PyList_GET_ITEM(obj, i), align);
+ if (conv == NULL) {
Py_DECREF(tup);
Py_DECREF(key);
goto fail;
@@ -1091,7 +1087,7 @@ _convert_from_dict(PyObject *obj, int align)
totalsize = 0;
for (i = 0; i < n; i++) {
PyObject *tup, *descr, *ind, *title, *name, *off;
- int len, ret, _align = 1;
+ int len, _align = 1;
PyArray_Descr *newdescr;
/* Build item to insert (descr, offset, [title])*/
@@ -1115,14 +1111,9 @@ _convert_from_dict(PyObject *obj, int align)
Py_DECREF(ind);
goto fail;
}
- if (align) {
- ret = PyArray_DescrAlignConverter(descr, &newdescr);
- }
- else {
- ret = PyArray_DescrConverter(descr, &newdescr);
- }
+ newdescr = _arraydescr_run_converter(descr, align);
Py_DECREF(descr);
- if (ret == NPY_FAIL) {
+ if (newdescr == NULL) {
Py_DECREF(tup);
Py_DECREF(ind);
goto fail;
@@ -1168,7 +1159,9 @@ _convert_from_dict(PyObject *obj, int align)
"not divisible by the field alignment %d "
"with align=True",
offset, newdescr->alignment);
- ret = NPY_FAIL;
+ Py_DECREF(ind);
+ Py_DECREF(tup);
+ goto fail;
}
else if (offset + newdescr->elsize > totalsize) {
totalsize = offset + newdescr->elsize;
@@ -1181,11 +1174,6 @@ _convert_from_dict(PyObject *obj, int align)
PyTuple_SET_ITEM(tup, 1, PyInt_FromLong(totalsize));
totalsize += newdescr->elsize;
}
- if (ret == NPY_FAIL) {
- Py_DECREF(ind);
- Py_DECREF(tup);
- goto fail;
- }
if (len == 3) {
PyTuple_SET_ITEM(tup, 2, title);
}
@@ -1223,9 +1211,6 @@ _convert_from_dict(PyObject *obj, int align)
}
}
Py_DECREF(tup);
- if (ret == NPY_FAIL) {
- goto fail;
- }
dtypeflags |= (newdescr->flags & NPY_FROM_FIELDS);
}
@@ -2281,12 +2266,8 @@ arraydescr_new(PyTypeObject *NPY_UNUSED(subtype),
return NULL;
}
- if (align) {
- if (!PyArray_DescrAlignConverter(odescr, &conv)) {
- return NULL;
- }
- }
- else if (!PyArray_DescrConverter(odescr, &conv)) {
+ conv = _arraydescr_run_converter(odescr, align);
+ if (conv == NULL) {
return NULL;
}