summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCharles Harris <charlesr.harris@gmail.com>2021-03-24 15:26:55 -0600
committerGitHub <noreply@github.com>2021-03-24 15:26:55 -0600
commit74e5bff6e54cae165c088cd38104071469eb3d84 (patch)
tree7baf107b7a4efc523571872055f9d692435680d8
parentbadbf70324274bdb4299d8c64d3d83a26be2d4c0 (diff)
parentbebf5c891cb1c547b6de97ecd48af66853841028 (diff)
downloadnumpy-74e5bff6e54cae165c088cd38104071469eb3d84.tar.gz
Merge pull request #15271 from seberg/splitup-faster-argparsing
ENH: Optimize and cleanup ufunc calls and ufunc CheckOverrides
-rw-r--r--doc/release/upcoming_changes/15271.compatibility.rst31
-rw-r--r--numpy/core/include/numpy/ufuncobject.h4
-rw-r--r--numpy/core/src/umath/override.c524
-rw-r--r--numpy/core/src/umath/override.h7
-rw-r--r--numpy/core/src/umath/ufunc_object.c1478
-rw-r--r--numpy/core/src/umath/ufunc_object.h14
-rw-r--r--numpy/core/src/umath/umathmodule.c28
-rw-r--r--numpy/core/tests/test_ufunc.py6
-rw-r--r--numpy/core/tests/test_umath.py14
9 files changed, 929 insertions, 1177 deletions
diff --git a/doc/release/upcoming_changes/15271.compatibility.rst b/doc/release/upcoming_changes/15271.compatibility.rst
new file mode 100644
index 000000000..7deefe256
--- /dev/null
+++ b/doc/release/upcoming_changes/15271.compatibility.rst
@@ -0,0 +1,31 @@
+Error type changes in universal functions
+-----------------------------------------
+The universal functions may now raise different errors
+on invalid input in some cases.
+The main changes should be that a ``RuntimeError`` was
+replaced with a more fitting ``TypeError``.
+When multiple errors were present in the same call,
+NumPy may now raise a different one.
+
+
+``__array_ufunc__`` argument validation
+---------------------------------------
+NumPy will now partially validate arguments before calling
+``__array_ufunc__``. Previously, it was possible to pass
+on invalid arguments (such as a non-existing keyword
+argument) when dispatch was known to occur.
+
+
+``__array_ufunc__`` and additional positional arguments
+-------------------------------------------------------
+Previously, all positionally passed arguments were checked for
+``__array_ufunc__`` support. In the case of ``reduce``,
+``accumulate``, and ``reduceat`` all arguments may be passed by
+position. This means that when they were passed by
+position, they could previously have been asked to handle
+the ufunc call via ``__array_ufunc__``.
+Since this depended on the way the arguments were passed
+(by position or by keyword), NumPy will now only dispatch
+on the input and output array.
+For example, NumPy will never dispatch on the ``where`` array
+in a reduction such as ``np.add.reduce``.
diff --git a/numpy/core/include/numpy/ufuncobject.h b/numpy/core/include/numpy/ufuncobject.h
index e5d845842..333a326ee 100644
--- a/numpy/core/include/numpy/ufuncobject.h
+++ b/numpy/core/include/numpy/ufuncobject.h
@@ -194,7 +194,11 @@ typedef struct _tagPyUFuncObject {
* but this was never implemented. (This is also why the above
* selector is called the "legacy" selector.)
*/
+ #if PY_VERSION_HEX >= 0x03080000
+ vectorcallfunc vectorcall;
+ #else
void *reserved2;
+ #endif
/*
* A function which returns a masked inner loop for the ufunc.
*/
diff --git a/numpy/core/src/umath/override.c b/numpy/core/src/umath/override.c
index a0090e302..d247c2639 100644
--- a/numpy/core/src/umath/override.c
+++ b/numpy/core/src/umath/override.c
@@ -8,6 +8,7 @@
#include "override.h"
#include "ufunc_override.h"
+
/*
* For each positional argument and each argument in a possible "out"
* keyword, look for overrides of the standard ufunc behaviour, i.e.,
@@ -22,25 +23,16 @@
* Returns -1 on failure.
*/
static int
-get_array_ufunc_overrides(PyObject *args, PyObject *kwds,
+get_array_ufunc_overrides(PyObject *in_args, PyObject *out_args,
PyObject **with_override, PyObject **methods)
{
int i;
int num_override_args = 0;
- int narg, nout = 0;
- PyObject *out_kwd_obj;
- PyObject **arg_objs, **out_objs;
+ int narg, nout;
- narg = PyTuple_Size(args);
- if (narg < 0) {
- return -1;
- }
- arg_objs = PySequence_Fast_ITEMS(args);
-
- nout = PyUFuncOverride_GetOutObjects(kwds, &out_kwd_obj, &out_objs);
- if (nout < 0) {
- return -1;
- }
+ narg = (int)PyTuple_GET_SIZE(in_args);
+ /* It is valid for out_args to be NULL: */
+ nout = (out_args != NULL) ? (int)PyTuple_GET_SIZE(out_args) : 0;
for (i = 0; i < narg + nout; ++i) {
PyObject *obj;
@@ -48,10 +40,10 @@ get_array_ufunc_overrides(PyObject *args, PyObject *kwds,
int new_class = 1;
if (i < narg) {
- obj = arg_objs[i];
+ obj = PyTuple_GET_ITEM(in_args, i);
}
else {
- obj = out_objs[i - narg];
+ obj = PyTuple_GET_ITEM(out_args, i - narg);
}
/*
* Have we seen this class before? If so, ignore.
@@ -86,7 +78,6 @@ get_array_ufunc_overrides(PyObject *args, PyObject *kwds,
++num_override_args;
}
}
- Py_DECREF(out_kwd_obj);
return num_override_args;
fail:
@@ -94,359 +85,116 @@ fail:
Py_DECREF(with_override[i]);
Py_DECREF(methods[i]);
}
- Py_DECREF(out_kwd_obj);
return -1;
}
-/*
- * The following functions normalize ufunc arguments. The work done is similar
- * to what is done inside ufunc_object by get_ufunc_arguments for __call__ and
- * generalized ufuncs, and by PyUFunc_GenericReduction for the other methods.
- * It would be good to unify (see gh-8892).
- */
/*
- * ufunc() and ufunc.outer() accept 'sig' or 'signature';
- * normalize to 'signature'
+ * Build a dictionary from the keyword arguments, but replace out with the
+ * normalized version (and always pass it even if it was passed by position).
*/
static int
-normalize_signature_keyword(PyObject *normal_kwds)
-{
- PyObject *obj = _PyDict_GetItemStringWithError(normal_kwds, "sig");
- if (obj == NULL && PyErr_Occurred()){
- return -1;
- }
- if (obj != NULL) {
- PyObject *sig = _PyDict_GetItemStringWithError(normal_kwds, "signature");
- if (sig == NULL && PyErr_Occurred()) {
- return -1;
- }
- if (sig) {
- PyErr_SetString(PyExc_TypeError,
- "cannot specify both 'sig' and 'signature'");
- return -1;
- }
- /*
- * No INCREF or DECREF needed: got a borrowed reference above,
- * and, unlike e.g. PyList_SetItem, PyDict_SetItem INCREF's it.
- */
- PyDict_SetItemString(normal_kwds, "signature", obj);
- PyDict_DelItemString(normal_kwds, "sig");
- }
- return 0;
-}
-
-static int
-normalize___call___args(PyUFuncObject *ufunc, PyObject *args,
- PyObject **normal_args, PyObject **normal_kwds)
+initialize_normal_kwds(PyObject *out_args,
+ PyObject *const *args, Py_ssize_t len_args, PyObject *kwnames,
+ PyObject *normal_kwds)
{
- /*
- * ufunc.__call__(*args, **kwds)
- */
- npy_intp i;
- int not_all_none;
- npy_intp nin = ufunc->nin;
- npy_intp nout = ufunc->nout;
- npy_intp nargs = PyTuple_GET_SIZE(args);
- npy_intp nkwds = PyDict_Size(*normal_kwds);
- PyObject *obj;
-
- if (nargs < nin) {
- PyErr_Format(PyExc_TypeError,
- "ufunc() missing %"NPY_INTP_FMT" of %"NPY_INTP_FMT
- "required positional argument(s)", nin - nargs, nin);
- return -1;
- }
- if (nargs > nin+nout) {
- PyErr_Format(PyExc_TypeError,
- "ufunc() takes from %"NPY_INTP_FMT" to %"NPY_INTP_FMT
- "arguments but %"NPY_INTP_FMT" were given",
- nin, nin+nout, nargs);
- return -1;
- }
-
- *normal_args = PyTuple_GetSlice(args, 0, nin);
- if (*normal_args == NULL) {
- return -1;
- }
-
- /* If we have more args than nin, they must be the output variables.*/
- if (nargs > nin) {
- if (nkwds > 0) {
- PyObject *out_kwd = _PyDict_GetItemStringWithError(*normal_kwds, "out");
- if (out_kwd == NULL && PyErr_Occurred()) {
- return -1;
- }
- else if (out_kwd) {
- PyErr_Format(PyExc_TypeError,
- "argument given by name ('out') and position "
- "(%"NPY_INTP_FMT")", nin);
+ if (kwnames != NULL) {
+ for (Py_ssize_t i = 0; i < PyTuple_GET_SIZE(kwnames); i++) {
+ if (PyDict_SetItem(normal_kwds,
+ PyTuple_GET_ITEM(kwnames, i), args[i + len_args]) < 0) {
return -1;
}
}
- for (i = nin; i < nargs; i++) {
- not_all_none = (PyTuple_GET_ITEM(args, i) != Py_None);
- if (not_all_none) {
- break;
- }
- }
- if (not_all_none) {
- if (nargs - nin == nout) {
- obj = PyTuple_GetSlice(args, nin, nargs);
- }
- else {
- PyObject *item;
-
- obj = PyTuple_New(nout);
- if (obj == NULL) {
- return -1;
- }
- for (i = 0; i < nout; i++) {
- if (i + nin < nargs) {
- item = PyTuple_GET_ITEM(args, nin+i);
- }
- else {
- item = Py_None;
- }
- Py_INCREF(item);
- PyTuple_SET_ITEM(obj, i, item);
- }
- }
- PyDict_SetItemString(*normal_kwds, "out", obj);
- Py_DECREF(obj);
- }
}
- /* gufuncs accept either 'axes' or 'axis', but not both */
- if (nkwds >= 2) {
- PyObject *axis_kwd = _PyDict_GetItemStringWithError(*normal_kwds, "axis");
- if (axis_kwd == NULL && PyErr_Occurred()) {
+ static PyObject *out_str = NULL;
+ if (out_str == NULL) {
+ out_str = PyUnicode_InternFromString("out");
+ if (out_str == NULL) {
return -1;
}
- PyObject *axes_kwd = _PyDict_GetItemStringWithError(*normal_kwds, "axes");
- if (axes_kwd == NULL && PyErr_Occurred()) {
- return -1;
- }
- if (axis_kwd && axes_kwd) {
- PyErr_SetString(PyExc_TypeError,
- "cannot specify both 'axis' and 'axes'");
- return -1;
- }
- }
- /* finally, ufuncs accept 'sig' or 'signature' normalize to 'signature' */
- return nkwds == 0 ? 0 : normalize_signature_keyword(*normal_kwds);
-}
-
-static int
-normalize_reduce_args(PyUFuncObject *ufunc, PyObject *args,
- PyObject **normal_args, PyObject **normal_kwds)
-{
- /*
- * ufunc.reduce(a[, axis, dtype, out, keepdims])
- */
- npy_intp nargs = PyTuple_GET_SIZE(args);
- npy_intp i;
- PyObject *obj;
- static PyObject *NoValue = NULL;
- static char *kwlist[] = {"array", "axis", "dtype", "out", "keepdims",
- "initial", "where"};
-
- npy_cache_import("numpy", "_NoValue", &NoValue);
- if (NoValue == NULL) return -1;
-
- if (nargs < 1 || nargs > 7) {
- PyErr_Format(PyExc_TypeError,
- "ufunc.reduce() takes from 1 to 7 positional "
- "arguments but %"NPY_INTP_FMT" were given", nargs);
- return -1;
- }
- *normal_args = PyTuple_GetSlice(args, 0, 1);
- if (*normal_args == NULL) {
- return -1;
}
- for (i = 1; i < nargs; i++) {
- PyObject *kwd = _PyDict_GetItemStringWithError(*normal_kwds, kwlist[i]);
- if (kwd == NULL && PyErr_Occurred()) {
+ if (out_args != NULL) {
+ /* Replace `out` argument with the normalized version */
+ int res = PyDict_SetItem(normal_kwds, out_str, out_args);
+ if (res < 0) {
return -1;
}
- else if (kwd) {
- PyErr_Format(PyExc_TypeError,
- "argument given by name ('%s') and position "
- "(%"NPY_INTP_FMT")", kwlist[i], i);
+ }
+ else {
+ /* Ensure that `out` is not present. */
+ int res = PyDict_Contains(normal_kwds, out_str);
+ if (res < 0) {
return -1;
}
- obj = PyTuple_GET_ITEM(args, i);
- if (i == 3) {
- /* remove out=None */
- if (obj == Py_None) {
- continue;
- }
- obj = PyTuple_GetSlice(args, 3, 4);
- }
- /* Remove initial=np._NoValue */
- if (i == 5 && obj == NoValue) {
- continue;
- }
- PyDict_SetItemString(*normal_kwds, kwlist[i], obj);
- if (i == 3) {
- Py_DECREF(obj);
+ if (res) {
+ return PyDict_DelItem(normal_kwds, out_str);
}
}
return 0;
}
+/*
+ * ufunc() and ufunc.outer() accept 'sig' or 'signature'. We guarantee
+ * that it is passed as 'signature' by renaming 'sig' if present.
+ * Note that we have already validated that only one of them was passed
+ * before checking for overrides.
+ */
static int
-normalize_accumulate_args(PyUFuncObject *ufunc, PyObject *args,
- PyObject **normal_args, PyObject **normal_kwds)
+normalize_signature_keyword(PyObject *normal_kwds)
{
- /*
- * ufunc.accumulate(a[, axis, dtype, out])
- */
- npy_intp nargs = PyTuple_GET_SIZE(args);
- npy_intp i;
- PyObject *obj;
- static char *kwlist[] = {"array", "axis", "dtype", "out", "keepdims"};
-
- if (nargs < 1 || nargs > 4) {
- PyErr_Format(PyExc_TypeError,
- "ufunc.accumulate() takes from 1 to 4 positional "
- "arguments but %"NPY_INTP_FMT" were given", nargs);
- return -1;
- }
- *normal_args = PyTuple_GetSlice(args, 0, 1);
- if (*normal_args == NULL) {
+ /* If the keywords include `sig` rename to `signature`. */
+ PyObject* obj = _PyDict_GetItemStringWithError(normal_kwds, "sig");
+ if (obj == NULL && PyErr_Occurred()) {
return -1;
}
-
- for (i = 1; i < nargs; i++) {
- PyObject *kwd = _PyDict_GetItemStringWithError(*normal_kwds, kwlist[i]);
- if (kwd == NULL && PyErr_Occurred()) {
+ if (obj != NULL) {
+ /*
+ * No INCREF or DECREF needed: got a borrowed reference above,
+ * and, unlike e.g. PyList_SetItem, PyDict_SetItem INCREF's it.
+ */
+ if (PyDict_SetItemString(normal_kwds, "signature", obj) < 0) {
return -1;
}
- else if (kwd) {
- PyErr_Format(PyExc_TypeError,
- "argument given by name ('%s') and position "
- "(%"NPY_INTP_FMT")", kwlist[i], i);
+ if (PyDict_DelItemString(normal_kwds, "sig") < 0) {
return -1;
}
- obj = PyTuple_GET_ITEM(args, i);
- if (i == 3) {
- /* remove out=None */
- if (obj == Py_None) {
- continue;
- }
- obj = PyTuple_GetSlice(args, 3, 4);
- }
- PyDict_SetItemString(*normal_kwds, kwlist[i], obj);
- if (i == 3) {
- Py_DECREF(obj);
- }
}
return 0;
}
+
static int
-normalize_reduceat_args(PyUFuncObject *ufunc, PyObject *args,
- PyObject **normal_args, PyObject **normal_kwds)
+copy_positional_args_to_kwargs(const char **keywords,
+ PyObject *const *args, Py_ssize_t len_args,
+ PyObject *normal_kwds)
{
- /*
- * ufunc.reduceat(a, indices[, axis, dtype, out])
- * the number of arguments has been checked in PyUFunc_GenericReduction.
- */
- npy_intp i;
- npy_intp nargs = PyTuple_GET_SIZE(args);
- PyObject *obj;
- static char *kwlist[] = {"array", "indices", "axis", "dtype", "out"};
-
- if (nargs < 2 || nargs > 5) {
- PyErr_Format(PyExc_TypeError,
- "ufunc.reduceat() takes from 2 to 4 positional "
- "arguments but %"NPY_INTP_FMT" were given", nargs);
- return -1;
- }
- /* a and indices */
- *normal_args = PyTuple_GetSlice(args, 0, 2);
- if (*normal_args == NULL) {
- return -1;
- }
-
- for (i = 2; i < nargs; i++) {
- PyObject *kwd = _PyDict_GetItemStringWithError(*normal_kwds, kwlist[i]);
- if (kwd == NULL && PyErr_Occurred()) {
- return -1;
- }
- else if (kwd) {
- PyErr_Format(PyExc_TypeError,
- "argument given by name ('%s') and position "
- "(%"NPY_INTP_FMT")", kwlist[i], i);
- return -1;
+ for (Py_ssize_t i = 0; i < len_args; i++) {
+ if (keywords[i] == NULL) {
+ /* keyword argument is either input or output and not set here */
+ continue;
}
- obj = PyTuple_GET_ITEM(args, i);
- if (i == 4) {
- /* remove out=None */
- if (obj == Py_None) {
+ if (NPY_UNLIKELY(i == 5)) {
+ /*
+ * This is only relevant for reduce, which is the only one with
+ * 5 keyword arguments.
+ */
+ static PyObject *NoValue = NULL;
+ assert(strcmp(keywords[i], "initial") == 0);
+ npy_cache_import("numpy", "_NoValue", &NoValue);
+ if (args[i] == NoValue) {
continue;
}
- obj = PyTuple_GetSlice(args, 4, 5);
}
- PyDict_SetItemString(*normal_kwds, kwlist[i], obj);
- if (i == 4) {
- Py_DECREF(obj);
+
+ int res = PyDict_SetItemString(normal_kwds, keywords[i], args[i]);
+ if (res < 0) {
+ return -1;
}
}
return 0;
}
-static int
-normalize_outer_args(PyUFuncObject *ufunc, PyObject *args,
- PyObject **normal_args, PyObject **normal_kwds)
-{
- /*
- * ufunc.outer(*args, **kwds)
- * all positional arguments should be inputs.
- * for the keywords, we only need to check 'sig' vs 'signature'.
- */
- npy_intp nin = ufunc->nin;
- npy_intp nargs = PyTuple_GET_SIZE(args);
-
- if (nargs < nin) {
- PyErr_Format(PyExc_TypeError,
- "ufunc.outer() missing %"NPY_INTP_FMT" of %"NPY_INTP_FMT
- "required positional " "argument(s)", nin - nargs, nin);
- return -1;
- }
- if (nargs > nin) {
- PyErr_Format(PyExc_TypeError,
- "ufunc.outer() takes %"NPY_INTP_FMT" arguments but"
- "%"NPY_INTP_FMT" were given", nin, nargs);
- return -1;
- }
-
- *normal_args = PyTuple_GetSlice(args, 0, nin);
- if (*normal_args == NULL) {
- return -1;
- }
- /* ufuncs accept 'sig' or 'signature' normalize to 'signature' */
- return normalize_signature_keyword(*normal_kwds);
-}
-
-static int
-normalize_at_args(PyUFuncObject *ufunc, PyObject *args,
- PyObject **normal_args, PyObject **normal_kwds)
-{
- /* ufunc.at(a, indices[, b]) */
- npy_intp nargs = PyTuple_GET_SIZE(args);
-
- if (nargs < 2 || nargs > 3) {
- PyErr_Format(PyExc_TypeError,
- "ufunc.at() takes from 2 to 3 positional "
- "arguments but %"NPY_INTP_FMT" were given", nargs);
- return -1;
- }
- *normal_args = PyTuple_GetSlice(args, 0, nargs);
- return (*normal_args == NULL);
-}
-
/*
* Check a set of args for the `__array_ufunc__` method. If more than one of
* the input arguments implements `__array_ufunc__`, they are tried in the
@@ -460,31 +208,26 @@ normalize_at_args(PyUFuncObject *ufunc, PyObject *args,
*/
NPY_NO_EXPORT int
PyUFunc_CheckOverride(PyUFuncObject *ufunc, char *method,
- PyObject *args, PyObject *kwds,
- PyObject **result)
+ PyObject *in_args, PyObject *out_args,
+ PyObject *const *args, Py_ssize_t len_args, PyObject *kwnames,
+ PyObject **result)
{
- int i;
- int j;
int status;
int num_override_args;
PyObject *with_override[NPY_MAXARGS];
PyObject *array_ufunc_methods[NPY_MAXARGS];
- PyObject *out;
-
PyObject *method_name = NULL;
- PyObject *normal_args = NULL; /* normal_* holds normalized arguments. */
PyObject *normal_kwds = NULL;
PyObject *override_args = NULL;
- Py_ssize_t len;
/*
* Check inputs for overrides
*/
num_override_args = get_array_ufunc_overrides(
- args, kwds, with_override, array_ufunc_methods);
+ in_args, out_args, with_override, array_ufunc_methods);
if (num_override_args == -1) {
goto fail;
}
@@ -495,104 +238,58 @@ PyUFunc_CheckOverride(PyUFuncObject *ufunc, char *method,
}
/*
- * Normalize ufunc arguments.
+ * Normalize ufunc arguments, note that any input and output arguments
+ * have already been stored in `in_args` and `out_args`.
*/
-
- /* Build new kwds */
- if (kwds && PyDict_CheckExact(kwds)) {
-
- /* ensure out is always a tuple */
- normal_kwds = PyDict_Copy(kwds);
- out = _PyDict_GetItemStringWithError(normal_kwds, "out");
- if (out == NULL && PyErr_Occurred()) {
- goto fail;
- }
- else if (out) {
- int nout = ufunc->nout;
-
- if (PyTuple_CheckExact(out)) {
- int all_none = 1;
-
- if (PyTuple_GET_SIZE(out) != nout) {
- PyErr_Format(PyExc_ValueError,
- "The 'out' tuple must have exactly "
- "%d entries: one per ufunc output", nout);
- goto fail;
- }
- for (i = 0; i < PyTuple_GET_SIZE(out); i++) {
- all_none = (PyTuple_GET_ITEM(out, i) == Py_None);
- if (!all_none) {
- break;
- }
- }
- if (all_none) {
- PyDict_DelItemString(normal_kwds, "out");
- }
- }
- else {
- /* not a tuple */
- if (nout > 1) {
- PyErr_SetString(PyExc_TypeError,
- "'out' must be a tuple of arguments");
- goto fail;
- }
- if (out != Py_None) {
- /* not already a tuple and not None */
- PyObject *out_tuple = PyTuple_New(1);
-
- if (out_tuple == NULL) {
- goto fail;
- }
- /* out was borrowed ref; make it permanent */
- Py_INCREF(out);
- /* steals reference */
- PyTuple_SET_ITEM(out_tuple, 0, out);
- PyDict_SetItemString(normal_kwds, "out", out_tuple);
- Py_DECREF(out_tuple);
- }
- else {
- /* out=None; remove it */
- PyDict_DelItemString(normal_kwds, "out");
- }
- }
- }
- }
- else {
- normal_kwds = PyDict_New();
- }
+ normal_kwds = PyDict_New();
if (normal_kwds == NULL) {
goto fail;
}
+ if (initialize_normal_kwds(out_args,
+ args, len_args, kwnames, normal_kwds) < 0) {
+ goto fail;
+ }
- /* decide what to do based on the method. */
+ /*
+ * Reduce-like methods can pass keyword arguments also by position,
+ * in which case the additional positional arguments have to be copied
+ * into the keyword argument dictionary. The `__call__` and `__outer__`
+ * method have to normalize sig and signature.
+ */
/* ufunc.__call__ */
if (strcmp(method, "__call__") == 0) {
- status = normalize___call___args(ufunc, args, &normal_args,
- &normal_kwds);
+ status = normalize_signature_keyword(normal_kwds);
}
/* ufunc.reduce */
else if (strcmp(method, "reduce") == 0) {
- status = normalize_reduce_args(ufunc, args, &normal_args,
- &normal_kwds);
+ static const char *keywords[] = {
+ NULL, "axis", "dtype", NULL, "keepdims",
+ "initial", "where"};
+ status = copy_positional_args_to_kwargs(keywords,
+ args, len_args, normal_kwds);
}
/* ufunc.accumulate */
else if (strcmp(method, "accumulate") == 0) {
- status = normalize_accumulate_args(ufunc, args, &normal_args,
- &normal_kwds);
+ static const char *keywords[] = {
+ NULL, "axis", "dtype", NULL};
+ status = copy_positional_args_to_kwargs(keywords,
+ args, len_args, normal_kwds);
}
/* ufunc.reduceat */
else if (strcmp(method, "reduceat") == 0) {
- status = normalize_reduceat_args(ufunc, args, &normal_args,
- &normal_kwds);
+ static const char *keywords[] = {
+ NULL, NULL, "axis", "dtype", NULL};
+ status = copy_positional_args_to_kwargs(keywords,
+ args, len_args, normal_kwds);
}
- /* ufunc.outer */
+ /* ufunc.outer (identical to call) */
else if (strcmp(method, "outer") == 0) {
- status = normalize_outer_args(ufunc, args, &normal_args, &normal_kwds);
+ status = normalize_signature_keyword(normal_kwds);
}
/* ufunc.at */
else if (strcmp(method, "at") == 0) {
- status = normalize_at_args(ufunc, args, &normal_args, &normal_kwds);
+ status = 0;
}
/* unknown method */
else {
@@ -610,7 +307,7 @@ PyUFunc_CheckOverride(PyUFuncObject *ufunc, char *method,
goto fail;
}
- len = PyTuple_GET_SIZE(normal_args);
+ int len = (int)PyTuple_GET_SIZE(in_args);
/* Call __array_ufunc__ functions in correct order */
while (1) {
@@ -621,14 +318,14 @@ PyUFunc_CheckOverride(PyUFuncObject *ufunc, char *method,
*result = NULL;
/* Choose an overriding argument */
- for (i = 0; i < num_override_args; i++) {
+ for (int i = 0; i < num_override_args; i++) {
override_obj = with_override[i];
if (override_obj == NULL) {
continue;
}
/* Check for sub-types to the right of obj. */
- for (j = i + 1; j < num_override_args; j++) {
+ for (int j = i + 1; j < num_override_args; j++) {
PyObject *other_obj = with_override[j];
if (other_obj != NULL &&
Py_TYPE(other_obj) != Py_TYPE(override_obj) &&
@@ -662,8 +359,8 @@ PyUFunc_CheckOverride(PyUFuncObject *ufunc, char *method,
PyTuple_SET_ITEM(override_args, 1, (PyObject *)ufunc);
Py_INCREF(method_name);
PyTuple_SET_ITEM(override_args, 2, method_name);
- for (i = 0; i < len; i++) {
- PyObject *item = PyTuple_GET_ITEM(normal_args, i);
+ for (int i = 0; i < len; i++) {
+ PyObject *item = PyTuple_GET_ITEM(in_args, i);
Py_INCREF(item);
PyTuple_SET_ITEM(override_args, i + 3, item);
@@ -724,11 +421,10 @@ PyUFunc_CheckOverride(PyUFuncObject *ufunc, char *method,
fail:
status = -1;
cleanup:
- for (i = 0; i < num_override_args; i++) {
+ for (int i = 0; i < num_override_args; i++) {
Py_XDECREF(with_override[i]);
Py_XDECREF(array_ufunc_methods[i]);
}
- Py_XDECREF(normal_args);
Py_XDECREF(method_name);
Py_XDECREF(normal_kwds);
return status;
diff --git a/numpy/core/src/umath/override.h b/numpy/core/src/umath/override.h
index 68f3c6ef0..4e9a323ca 100644
--- a/numpy/core/src/umath/override.h
+++ b/numpy/core/src/umath/override.h
@@ -6,6 +6,9 @@
NPY_NO_EXPORT int
PyUFunc_CheckOverride(PyUFuncObject *ufunc, char *method,
- PyObject *args, PyObject *kwds,
- PyObject **result);
+ PyObject *in_args, PyObject *out_args,
+ PyObject *const *args, Py_ssize_t len_args, PyObject *kwnames,
+ PyObject **result);
+
+
#endif
diff --git a/numpy/core/src/umath/ufunc_object.c b/numpy/core/src/umath/ufunc_object.c
index d70d15c50..6510709db 100644
--- a/numpy/core/src/umath/ufunc_object.c
+++ b/numpy/core/src/umath/ufunc_object.c
@@ -28,10 +28,11 @@
#define NPY_NO_DEPRECATED_API NPY_API_VERSION
#include "Python.h"
+#include "stddef.h"
#include "npy_config.h"
-
#include "npy_pycompat.h"
+#include "npy_argparse.h"
#include "numpy/arrayobject.h"
#include "numpy/ufuncobject.h"
@@ -95,6 +96,14 @@ _get_wrap_prepare_args(ufunc_full_args full_args) {
/* ---------------------------------------------------------------- */
+static PyObject *
+ufunc_generic_call_with_operands(
+ PyUFuncObject *ufunc, PyObject *args, PyObject *kwds,
+ PyArrayObject **operands_in);
+
+static PyObject *
+prepare_input_arguments_for_outer(PyObject *args, PyUFuncObject *ufunc);
+
static int
_does_loop_use_arrays(void *data);
@@ -270,7 +279,7 @@ _get_output_array_method(PyObject *obj, PyObject *method,
*/
static void
_find_array_prepare(ufunc_full_args args,
- PyObject **output_prep, int nin, int nout)
+ PyObject **output_prep, int nout)
{
int i;
PyObject *prep;
@@ -397,27 +406,19 @@ _ufunc_setup_flags(PyUFuncObject *ufunc, npy_uint32 op_in_flags,
* A NULL is placed in output_wrap for outputs that
* should just have PyArray_Return called.
*/
-static int
-_find_array_wrap(ufunc_full_args args, PyObject *kwds,
- PyObject **output_wrap, int nin, int nout)
+static void
+_find_array_wrap(ufunc_full_args args, npy_bool subok,
+ PyObject **output_wrap, int nin, int nout)
{
int i;
- PyObject *obj;
PyObject *wrap = NULL;
/*
* If a 'subok' parameter is passed and isn't True, don't wrap but put None
* into slots with out arguments which means return the out argument
*/
- if (kwds != NULL) {
- obj = PyDict_GetItemWithError(kwds, npy_um_str_subok);
- if (obj == NULL && PyErr_Occurred()) {
- return -1;
- }
- else if (obj != NULL && obj != Py_True) {
- /* skip search for wrap members */
- goto handle_out;
- }
+ if (!subok) {
+ goto handle_out;
}
/*
@@ -453,7 +454,6 @@ handle_out:
}
Py_XDECREF(wrap);
- return 0;
}
@@ -849,103 +849,12 @@ ufunc_get_name_cstr(PyUFuncObject *ufunc) {
return ufunc->name ? ufunc->name : "<unnamed ufunc>";
}
-/*
- * Helpers for keyword parsing
- */
-
-/*
- * Find key in a list of pointers to keyword names.
- * The list should end with NULL.
- *
- * Returns either the index into the list (pointing to the final key with NULL
- * if no match was found), or -1 on failure.
- */
-static npy_intp
-locate_key(PyObject **kwnames, PyObject *key)
-{
- PyObject **kwname = kwnames;
- while (*kwname != NULL && *kwname != key) {
- kwname++;
- }
- /* Slow fallback, just in case */
- if (*kwname == NULL) {
- int cmp = 0;
- kwname = kwnames;
- while (*kwname != NULL &&
- (cmp = PyObject_RichCompareBool(key, *kwname,
- Py_EQ)) == 0) {
- kwname++;
- }
- if (cmp < 0) {
- return -1;
- }
- }
- return kwname - kwnames;
-}
-
-/*
- * Parse keyword arguments, matching against kwnames
- *
- * Arguments beyond kwnames (the va_list) should contain converters and outputs
- * for each keyword name (where an output can be NULL to indicate the particular
- * keyword should be ignored).
- *
- * Returns 0 on success, -1 on failure with an error set.
- *
- * Note that the parser does not clean up on failure, i.e., already parsed keyword
- * values may hold new references, which the caller has to remove.
- *
- * TODO: ufunc is only used for the name in error messages; passing on the
- * name instead might be an option.
- *
- * TODO: instead of having this function ignore of keywords for which the
- * corresponding output is NULL, the calling routine should prepare the
- * correct list.
- */
-static int
-parse_ufunc_keywords(PyUFuncObject *ufunc, PyObject *kwds, PyObject **kwnames, ...)
-{
- va_list va;
- PyObject *key, *value;
- Py_ssize_t pos = 0;
- typedef int converter(PyObject *, void *);
-
- while (PyDict_Next(kwds, &pos, &key, &value)) {
- npy_intp i;
- converter *convert;
- void *output = NULL;
- npy_intp index = locate_key(kwnames, key);
- if (index < 0) {
- return -1;
- }
- if (kwnames[index]) {
- va_start(va, kwnames);
- for (i = 0; i <= index; i++) {
- convert = va_arg(va, converter *);
- output = va_arg(va, void *);
- }
- va_end(va);
- }
- if (output) {
- if (!convert(value, output)) {
- return -1;
- }
- }
- else {
- PyErr_Format(PyExc_TypeError,
- "'%S' is an invalid keyword to ufunc '%s'",
- key, ufunc_get_name_cstr(ufunc));
- return -1;
- }
- }
- return 0;
-}
/*
* Converters for use in parsing of keywords arguments.
*/
-NPY_NO_EXPORT int
-_subok_converter(PyObject *obj, int *subok)
+static int
+_subok_converter(PyObject *obj, npy_bool *subok)
{
if (PyBool_Check(obj)) {
*subok = (obj == Py_True);
@@ -958,7 +867,7 @@ _subok_converter(PyObject *obj, int *subok)
}
}
-NPY_NO_EXPORT int
+static int
_keepdims_converter(PyObject *obj, int *keepdims)
{
if (PyBool_Check(obj)) {
@@ -972,7 +881,7 @@ _keepdims_converter(PyObject *obj, int *keepdims)
}
}
-NPY_NO_EXPORT int
+static int
_wheremask_converter(PyObject *obj, PyArrayObject **wheremask)
{
/*
@@ -996,75 +905,32 @@ _wheremask_converter(PyObject *obj, PyArrayObject **wheremask)
}
}
-NPY_NO_EXPORT int
-_new_reference(PyObject *obj, PyObject **out)
-{
- Py_INCREF(obj);
- *out = obj;
- return NPY_SUCCEED;
-}
-
-NPY_NO_EXPORT int
-_borrowed_reference(PyObject *obj, PyObject **out)
-{
- *out = obj;
- return NPY_SUCCEED;
-}
/*
- * Parses the positional and keyword arguments for a generic ufunc call.
- * All returned arguments are new references (with optional ones NULL
- * if not present)
+ * Due to the array override, do the actual parameter conversion
+ * only in this step. This function takes the reference objects and
+ * parses them into the desired values.
+ * This function cleans up after itself and NULLs references on error,
+ * however, the caller has to ensure that `out_op[0:nargs]` and `out_whermeask`
+ * are NULL initialized.
*/
static int
-get_ufunc_arguments(PyUFuncObject *ufunc,
- PyObject *args, PyObject *kwds,
- PyArrayObject **out_op,
- NPY_ORDER *out_order,
- NPY_CASTING *out_casting,
- PyObject **out_extobj,
- PyObject **out_typetup, /* type: Tuple[np.dtype] */
- int *out_subok, /* bool */
- PyArrayObject **out_wheremask, /* PyArray of bool */
- PyObject **out_axes, /* type: List[Tuple[T]] */
- PyObject **out_axis, /* type: T */
- int *out_keepdims) /* bool */
+convert_ufunc_arguments(PyUFuncObject *ufunc,
+ ufunc_full_args full_args, PyArrayObject **out_op,
+ PyObject *order_obj, NPY_ORDER *out_order,
+ PyObject *casting_obj, NPY_CASTING *out_casting,
+ PyObject *subok_obj, npy_bool *out_subok,
+ PyObject *where_obj, PyArrayObject **out_wheremask, /* PyArray of bool */
+ PyObject *keepdims_obj, int *out_keepdims)
{
- int i, nargs;
int nin = ufunc->nin;
int nout = ufunc->nout;
int nop = ufunc->nargs;
PyObject *obj;
- PyArray_Descr *dtype = NULL;
- /*
- * Initialize output objects so caller knows when outputs and optional
- * arguments are set (also means we can safely XDECREF on failure).
- */
- for (i = 0; i < nop; i++) {
- out_op[i] = NULL;
- }
- *out_extobj = NULL;
- *out_typetup = NULL;
- if (out_axes != NULL) {
- *out_axes = NULL;
- }
- if (out_axis != NULL) {
- *out_axis = NULL;
- }
- if (out_wheremask != NULL) {
- *out_wheremask = NULL;
- }
-
- /* Check number of arguments */
- nargs = PyTuple_Size(args);
- if ((nargs < nin) || (nargs > nop)) {
- PyErr_SetString(PyExc_ValueError, "invalid number of arguments");
- return -1;
- }
- /* Get input arguments */
- for (i = 0; i < nin; ++i) {
- obj = PyTuple_GET_ITEM(args, i);
+ /* Convert and fill in input arguments */
+ for (int i = 0; i < nin; i++) {
+ obj = PyTuple_GET_ITEM(full_args.in, i);
if (PyArray_Check(obj)) {
PyArrayObject *obj_a = (PyArrayObject *)obj;
@@ -1080,148 +946,43 @@ get_ufunc_arguments(PyUFuncObject *ufunc,
}
}
- /* Get positional output arguments */
- for (i = nin; i < nargs; ++i) {
- obj = PyTuple_GET_ITEM(args, i);
- if (_set_out_array(obj, out_op + i) < 0) {
- goto fail;
+ /* Convert and fill in output arguments */
+ if (full_args.out != NULL) {
+ for (int i = 0; i < nout; i++) {
+ obj = PyTuple_GET_ITEM(full_args.out, i);
+ if (_set_out_array(obj, out_op + i + nin) < 0) {
+ goto fail;
+ }
}
}
/*
- * If keywords are present, get keyword output and other arguments.
- * Raise an error if anything else is present in the keyword dictionary.
+ * Convert most arguments manually here, since it is easier to handle
+ * the ufunc override if we first parse only to objects.
*/
- if (kwds) {
- PyObject *out_kwd = NULL;
- PyObject *sig = NULL;
- static PyObject *kwnames[13] = {NULL};
- if (kwnames[0] == NULL) {
- kwnames[0] = npy_um_str_out;
- kwnames[1] = npy_um_str_where;
- kwnames[2] = npy_um_str_axes;
- kwnames[3] = npy_um_str_axis;
- kwnames[4] = npy_um_str_keepdims;
- kwnames[5] = npy_um_str_casting;
- kwnames[6] = npy_um_str_order;
- kwnames[7] = npy_um_str_dtype;
- kwnames[8] = npy_um_str_subok;
- kwnames[9] = npy_um_str_signature;
- kwnames[10] = npy_um_str_sig;
- kwnames[11] = npy_um_str_extobj;
- kwnames[12] = NULL; /* sentinel */
- }
- /*
- * Parse using converters to calculate outputs
- * (NULL outputs are treated as indicating a keyword is not allowed).
- */
- if (parse_ufunc_keywords(
- ufunc, kwds, kwnames,
- _borrowed_reference, &out_kwd,
- _wheremask_converter, out_wheremask, /* new reference */
- _new_reference, out_axes,
- _new_reference, out_axis,
- _keepdims_converter, out_keepdims,
- PyArray_CastingConverter, out_casting,
- PyArray_OrderConverter, out_order,
- PyArray_DescrConverter2, &dtype, /* new reference */
- _subok_converter, out_subok,
- _new_reference, out_typetup,
- _borrowed_reference, &sig,
- _new_reference, out_extobj) < 0) {
- goto fail;
- }
- /*
- * Check that outputs were not passed as positional as well,
- * and that they are either None or an array.
- */
- if (out_kwd) { /* borrowed reference */
- /*
- * Output arrays are generally specified as a tuple of arrays
- * and None, but may be a single array or None for ufuncs
- * with a single output.
- */
- if (nargs > nin) {
- PyErr_SetString(PyExc_ValueError,
- "cannot specify 'out' as both a "
- "positional and keyword argument");
- goto fail;
- }
- if (PyTuple_CheckExact(out_kwd)) {
- if (PyTuple_GET_SIZE(out_kwd) != nout) {
- PyErr_SetString(PyExc_ValueError,
- "The 'out' tuple must have exactly "
- "one entry per ufunc output");
- goto fail;
- }
- /* 'out' must be a tuple of arrays and Nones */
- for(i = 0; i < nout; ++i) {
- PyObject *val = PyTuple_GET_ITEM(out_kwd, i);
- if (_set_out_array(val, out_op+nin+i) < 0) {
- goto fail;
- }
- }
- }
- else if (nout == 1) {
- /* Can be an array if it only has one output */
- if (_set_out_array(out_kwd, out_op + nin) < 0) {
- goto fail;
- }
- }
- else {
- PyErr_SetString(PyExc_TypeError,
- nout > 1 ? "'out' must be a tuple of arrays" :
- "'out' must be an array or a tuple with "
- "a single array");
- goto fail;
- }
- }
- /*
- * Check we did not get both axis and axes, or multiple ways
- * to define a signature.
- */
- if (out_axes != NULL && out_axis != NULL &&
- *out_axes != NULL && *out_axis != NULL) {
- PyErr_SetString(PyExc_TypeError,
- "cannot specify both 'axis' and 'axes'");
- goto fail;
- }
- if (sig) { /* borrowed reference */
- if (*out_typetup != NULL) {
- PyErr_SetString(PyExc_ValueError,
- "cannot specify both 'sig' and 'signature'");
- goto fail;
- }
- Py_INCREF(sig);
- *out_typetup = sig;
- }
- if (dtype) { /* new reference */
- if (*out_typetup != NULL) {
- PyErr_SetString(PyExc_RuntimeError,
- "cannot specify both 'signature' and 'dtype'");
- goto fail;
- }
- /* Note: "N" uses the reference */
- *out_typetup = Py_BuildValue("(N)", dtype);
- }
+ if (where_obj && !_wheremask_converter(where_obj, out_wheremask)) {
+ goto fail;
+ }
+ if (keepdims_obj && !_keepdims_converter(keepdims_obj, out_keepdims)) {
+ goto fail;
+ }
+ if (casting_obj && !PyArray_CastingConverter(casting_obj, out_casting)) {
+ goto fail;
+ }
+ if (order_obj && !PyArray_OrderConverter(order_obj, out_order)) {
+ goto fail;
+ }
+ if (subok_obj && !_subok_converter(subok_obj, out_subok)) {
+ goto fail;
}
return 0;
fail:
- Py_XDECREF(dtype);
- Py_XDECREF(*out_typetup);
- Py_XDECREF(*out_extobj);
if (out_wheremask != NULL) {
- Py_XDECREF(*out_wheremask);
- }
- if (out_axes != NULL) {
- Py_XDECREF(*out_axes);
+ Py_XSETREF(*out_wheremask, NULL);
}
- if (out_axis != NULL) {
- Py_XDECREF(*out_axis);
- }
- for (i = 0; i < nop; i++) {
- Py_XDECREF(out_op[i]);
+ for (int i = 0; i < nop; i++) {
+ Py_XSETREF(out_op[i], NULL);
}
return -1;
}
@@ -1882,122 +1643,6 @@ execute_fancy_ufunc_loop(PyUFuncObject *ufunc,
return NpyIter_Deallocate(iter);
}
-static npy_bool
-tuple_all_none(PyObject *tup) {
- npy_intp i;
- for (i = 0; i < PyTuple_GET_SIZE(tup); ++i) {
- if (PyTuple_GET_ITEM(tup, i) != Py_None) {
- return NPY_FALSE;
- }
- }
- return NPY_TRUE;
-}
-
-/*
- * Convert positional args and the out kwarg into an input and output tuple.
- *
- * If the output tuple would be all None, return NULL instead.
- *
- * This duplicates logic in many places, so further refactoring is needed:
- * - get_ufunc_arguments
- * - PyUFunc_WithOverride
- * - normalize___call___args
- */
-static int
-make_full_arg_tuple(
- ufunc_full_args *full_args,
- npy_intp nin, npy_intp nout,
- PyObject *args, PyObject *kwds)
-{
- PyObject *out_kwd = NULL;
- npy_intp nargs = PyTuple_GET_SIZE(args);
- npy_intp i;
-
- /* This should have been checked by the caller */
- assert(nin <= nargs && nargs <= nin + nout);
-
- /* Initialize so we can XDECREF safely */
- full_args->in = NULL;
- full_args->out = NULL;
-
- /* Get the input arguments*/
- full_args->in = PyTuple_GetSlice(args, 0, nin);
- if (full_args->in == NULL) {
- goto fail;
- }
-
- /* Look for output keyword arguments */
- if (kwds) {
- out_kwd = PyDict_GetItemWithError(kwds, npy_um_str_out);
- if (out_kwd == NULL && PyErr_Occurred()) {
- goto fail;
- }
- }
- else {
- out_kwd = NULL;
- }
-
- if (out_kwd != NULL) {
- assert(nargs == nin);
- if (out_kwd == Py_None) {
- return 0;
- }
- else if (PyTuple_Check(out_kwd)) {
- assert(PyTuple_GET_SIZE(out_kwd) == nout);
- if (tuple_all_none(out_kwd)) {
- return 0;
- }
- Py_INCREF(out_kwd);
- full_args->out = out_kwd;
- return 0;
- }
- else {
- /* A single argument x is promoted to (x, None, None ...) */
- full_args->out = PyTuple_New(nout);
- if (full_args->out == NULL) {
- goto fail;
- }
- Py_INCREF(out_kwd);
- PyTuple_SET_ITEM(full_args->out, 0, out_kwd);
- for (i = 1; i < nout; ++i) {
- Py_INCREF(Py_None);
- PyTuple_SET_ITEM(full_args->out, i, Py_None);
- }
- return 0;
- }
- }
-
- /* No outputs in kwargs; if also none in args, we're done */
- if (nargs == nin) {
- return 0;
- }
- /* copy across positional output arguments, adding trailing Nones */
- full_args->out = PyTuple_New(nout);
- if (full_args->out == NULL) {
- goto fail;
- }
- for (i = nin; i < nargs; ++i) {
- PyObject *item = PyTuple_GET_ITEM(args, i);
- Py_INCREF(item);
- PyTuple_SET_ITEM(full_args->out, i - nin, item);
- }
- for (i = nargs; i < nin + nout; ++i) {
- Py_INCREF(Py_None);
- PyTuple_SET_ITEM(full_args->out, i - nin, Py_None);
- }
-
- /* don't return a tuple full of None */
- if (tuple_all_none(full_args->out)) {
- Py_DECREF(full_args->out);
- full_args->out = NULL;
- }
- return 0;
-
-fail:
- Py_XDECREF(full_args->in);
- Py_XDECREF(full_args->out);
- return -1;
-}
/*
* Validate that operands have enough dimensions, accounting for
@@ -2487,14 +2132,15 @@ _initialize_variable_parts(PyUFuncObject *ufunc,
}
static int
-PyUFunc_GeneralizedFunction(PyUFuncObject *ufunc,
- PyObject *args, PyObject *kwds,
- PyArrayObject **op)
+PyUFunc_GeneralizedFunctionInternal(PyUFuncObject *ufunc, PyArrayObject **op,
+ ufunc_full_args full_args, PyObject *type_tup, PyObject *extobj,
+ NPY_CASTING casting, NPY_ORDER order, npy_bool subok,
+ PyObject *axis, PyObject *axes, int keepdims)
{
int nin, nout;
int i, j, idim, nop;
const char *ufunc_name;
- int retval, subok = 1;
+ int retval;
int needs_api = 0;
PyArray_Descr *dtypes[NPY_MAXARGS];
@@ -2531,20 +2177,6 @@ PyUFunc_GeneralizedFunction(PyUFuncObject *ufunc,
int **remap_axis = NULL;
/* The __array_prepare__ function to call for each output */
PyObject *arr_prep[NPY_MAXARGS];
- /* The separated input and output arguments, parsed from args and kwds */
- ufunc_full_args full_args = {NULL, NULL};
-
- NPY_ORDER order = NPY_KEEPORDER;
- /* Use the default assignment casting rule */
- NPY_CASTING casting = NPY_DEFAULT_ASSIGN_CASTING;
- /* other possible keyword arguments */
- PyObject *extobj, *type_tup, *axes, *axis;
- int keepdims = -1;
-
- if (ufunc == NULL) {
- PyErr_SetString(PyExc_ValueError, "function not supported");
- return -1;
- }
nin = ufunc->nin;
nout = ufunc->nout;
@@ -2566,18 +2198,6 @@ PyUFunc_GeneralizedFunction(PyUFuncObject *ufunc,
goto fail;
}
- NPY_UF_DBG_PRINT("Getting arguments\n");
-
- /*
- * Get all the arguments.
- */
- retval = get_ufunc_arguments(ufunc, args, kwds,
- op, &order, &casting, &extobj,
- &type_tup, &subok, NULL, &axes, &axis, &keepdims);
- if (retval < 0) {
- NPY_UF_DBG_PRINT("Failure in getting arguments\n");
- return retval;
- }
/*
* If keepdims was passed in (and thus changed from the initial value
* on top), check the gufunc is suitable, i.e., that its inputs share
@@ -2637,6 +2257,8 @@ PyUFunc_GeneralizedFunction(PyUFuncObject *ufunc,
/* Possibly remap axes. */
if (axes != NULL || axis != NULL) {
+ assert(!(axes != NULL && axis != NULL));
+
remap_axis = PyArray_malloc(sizeof(remap_axis[0]) * nop);
remap_axis_memory = PyArray_malloc(sizeof(remap_axis_memory[0]) *
nop * NPY_MAXDIMS);
@@ -2814,15 +2436,11 @@ PyUFunc_GeneralizedFunction(PyUFuncObject *ufunc,
#endif
if (subok) {
- if (make_full_arg_tuple(&full_args, nin, nout, args, kwds) < 0) {
- goto fail;
- }
-
/*
* Get the appropriate __array_prepare__ function to call
* for each output
*/
- _find_array_prepare(full_args, arr_prep, nin, nout);
+ _find_array_prepare(full_args, arr_prep, nout);
}
/* If the loop wants the arrays, provide them */
@@ -3036,12 +2654,6 @@ PyUFunc_GeneralizedFunction(PyUFuncObject *ufunc,
Py_XDECREF(dtypes[i]);
Py_XDECREF(arr_prep[i]);
}
- Py_XDECREF(type_tup);
- Py_XDECREF(extobj);
- Py_XDECREF(axes);
- Py_XDECREF(axis);
- Py_XDECREF(full_args.in);
- Py_XDECREF(full_args.out);
PyArray_free(remap_axis_memory);
PyArray_free(remap_axis);
@@ -3054,36 +2666,25 @@ fail:
PyArray_free(inner_strides);
NpyIter_Deallocate(iter);
for (i = 0; i < nop; ++i) {
- Py_XDECREF(op[i]);
- op[i] = NULL;
Py_XDECREF(dtypes[i]);
Py_XDECREF(arr_prep[i]);
}
- Py_XDECREF(type_tup);
- Py_XDECREF(extobj);
- Py_XDECREF(axes);
- Py_XDECREF(axis);
- Py_XDECREF(full_args.in);
- Py_XDECREF(full_args.out);
PyArray_free(remap_axis_memory);
PyArray_free(remap_axis);
return retval;
}
-/*
- * This generic function is called with the ufunc object, the arguments to it,
- * and an array of (pointers to) PyArrayObjects which are NULL.
- *
- * 'op' is an array of at least NPY_MAXARGS PyArrayObject *.
- */
+
static int
-PyUFunc_GenericFunction_int(PyUFuncObject *ufunc,
- PyObject *args, PyObject *kwds, PyArrayObject **op)
+PyUFunc_GenericFunctionInternal(PyUFuncObject *ufunc, PyArrayObject **op,
+ ufunc_full_args full_args, PyObject *type_tup, PyObject *extobj,
+ NPY_CASTING casting, NPY_ORDER order, npy_bool subok,
+ PyArrayObject *wheremask)
{
int nin, nout;
int i, nop;
const char *ufunc_name;
- int retval = -1, subok = 1;
+ int retval = -1;
npy_uint32 op_flags[NPY_MAXARGS];
npy_intp default_op_out_flags;
@@ -3092,32 +2693,11 @@ PyUFunc_GenericFunction_int(PyUFuncObject *ufunc,
/* These parameters come from extobj= or from a TLS global */
int buffersize = 0, errormask = 0;
- /* The mask provided in the 'where=' parameter */
- PyArrayObject *wheremask = NULL;
-
/* The __array_prepare__ function to call for each output */
PyObject *arr_prep[NPY_MAXARGS];
- /*
- * This is either args, or args with the out= parameter from
- * kwds added appropriately.
- */
- ufunc_full_args full_args = {NULL, NULL};
int trivial_loop_ok = 0;
- NPY_ORDER order = NPY_KEEPORDER;
- /* Use the default assignment casting rule */
- NPY_CASTING casting = NPY_DEFAULT_ASSIGN_CASTING;
- PyObject *extobj, *type_tup;
-
- if (ufunc == NULL) {
- PyErr_SetString(PyExc_ValueError, "function not supported");
- return -1;
- }
-
- if (ufunc->core_enabled) {
- return PyUFunc_GeneralizedFunction(ufunc, args, kwds, op);
- }
nin = ufunc->nin;
nout = ufunc->nout;
@@ -3133,17 +2713,6 @@ PyUFunc_GenericFunction_int(PyUFuncObject *ufunc,
arr_prep[i] = NULL;
}
- NPY_UF_DBG_PRINT("Getting arguments\n");
-
- /* Get all the arguments */
- retval = get_ufunc_arguments(ufunc, args, kwds,
- op, &order, &casting, &extobj,
- &type_tup, &subok, &wheremask, NULL, NULL, NULL);
- if (retval < 0) {
- NPY_UF_DBG_PRINT("Failure in getting arguments\n");
- return retval;
- }
-
/* Get the buffersize and errormask */
if (_get_bufsize_errmask(extobj, ufunc_name, &buffersize, &errormask) < 0) {
retval = -1;
@@ -3189,17 +2758,13 @@ PyUFunc_GenericFunction_int(PyUFuncObject *ufunc,
#endif
if (subok) {
- if (make_full_arg_tuple(&full_args, nin, nout, args, kwds) < 0) {
- goto fail;
- }
/*
* Get the appropriate __array_prepare__ function to call
* for each output
*/
- _find_array_prepare(full_args, arr_prep, nin, nout);
+ _find_array_prepare(full_args, arr_prep, nout);
}
-
/* Do the ufunc loop */
if (wheremask != NULL) {
NPY_UF_DBG_PRINT("Executing fancy inner loop\n");
@@ -3261,11 +2826,6 @@ PyUFunc_GenericFunction_int(PyUFuncObject *ufunc,
Py_XDECREF(dtypes[i]);
Py_XDECREF(arr_prep[i]);
}
- Py_XDECREF(type_tup);
- Py_XDECREF(extobj);
- Py_XDECREF(full_args.in);
- Py_XDECREF(full_args.out);
- Py_XDECREF(wheremask);
NPY_UF_DBG_PRINT("Returning success code 0\n");
@@ -3274,38 +2834,52 @@ PyUFunc_GenericFunction_int(PyUFuncObject *ufunc,
fail:
NPY_UF_DBG_PRINT1("Returning failure code %d\n", retval);
for (i = 0; i < nop; ++i) {
- Py_XDECREF(op[i]);
- op[i] = NULL;
Py_XDECREF(dtypes[i]);
Py_XDECREF(arr_prep[i]);
}
- Py_XDECREF(type_tup);
- Py_XDECREF(extobj);
- Py_XDECREF(full_args.in);
- Py_XDECREF(full_args.out);
- Py_XDECREF(wheremask);
return retval;
}
-/*UFUNC_API*/
+/*UFUNC_API
+ * This generic function is called with the ufunc object, the arguments to it,
+ * and an array of (pointers to) PyArrayObjects which are NULL.
+ *
+ * 'op' is an array of at least NPY_MAXARGS PyArrayObject *.
+ */
NPY_NO_EXPORT int
PyUFunc_GenericFunction(PyUFuncObject *ufunc,
PyObject *args, PyObject *kwds, PyArrayObject **op)
{
/* NumPy 1.19, 2020-01-24 */
if (DEPRECATE(
- "PyUFunc_GenericFunction() C-API function is deprecated "
- "and expected to be removed rapidly. If you are using it (i.e. see "
- "this warning/error), please notify the NumPy developers. "
- "As of now it is expected that any use case is served better by "
- "the direct use of `PyObject_Call(ufunc, args, kwargs)`. "
- "PyUFunc_GenericFunction function has slightly different "
- "untested behaviour.") < 0) {
+ "PyUFunc_GenericFunction() C-API function is deprecated "
+ "and expected to be removed rapidly. If you are using it (i.e. see "
+ "this warning/error), please notify the NumPy developers. "
+ "As of now it is expected that any use case is served better by "
+ "the direct use of `PyObject_Call(ufunc, args, kwargs)`. "
+ "PyUFunc_GenericFunction function has slightly different "
+ "untested behaviour.") < 0) {
+ return -1;
+ }
+ if (ufunc == NULL) {
+ PyErr_SetString(PyExc_ValueError, "function not supported");
return -1;
}
- return PyUFunc_GenericFunction_int(ufunc, args, kwds, op);
+ if (op == NULL) {
+ PyErr_SetString(PyExc_ValueError,
+ "PyUFunc_GenericFunction() op must not be NULL.");
+ return -1;
+ }
+
+ PyObject *res = ufunc_generic_call_with_operands(ufunc, args, kwds, op);
+ if (res == NULL) {
+ return -1;
+ }
+ assert(res == Py_None);
+ Py_DECREF(res);
+ return 0;
}
@@ -3621,13 +3195,9 @@ PyUFunc_Reduce(PyUFuncObject *ufunc, PyArrayObject *arr, PyArrayObject *out,
const char *ufunc_name = ufunc_get_name_cstr(ufunc);
/* These parameters come from a TLS global */
int buffersize = 0, errormask = 0;
- static PyObject *NoValue = NULL;
NPY_UF_DBG_PRINT1("\nEvaluating ufunc %s.reduce\n", ufunc_name);
- npy_cache_import("numpy", "_NoValue", &NoValue);
- if (NoValue == NULL) return NULL;
-
ndim = PyArray_NDIM(arr);
/* Create an array of flags for reduction */
@@ -3653,7 +3223,7 @@ PyUFunc_Reduce(PyUFuncObject *ufunc, PyArrayObject *arr, PyArrayObject *out,
}
/* Get the initial value */
- if (initial == NULL || initial == NoValue) {
+ if (initial == NULL) {
initial = identity;
/*
@@ -4432,32 +4002,101 @@ fail:
}
+static npy_bool
+tuple_all_none(PyObject *tup) {
+ npy_intp i;
+ for (i = 0; i < PyTuple_GET_SIZE(tup); ++i) {
+ if (PyTuple_GET_ITEM(tup, i) != Py_None) {
+ return NPY_FALSE;
+ }
+ }
+ return NPY_TRUE;
+}
+
+
+static int
+_set_full_args_out(int nout, PyObject *out_obj, ufunc_full_args *full_args)
+{
+ if (PyTuple_CheckExact(out_obj)) {
+ if (PyTuple_GET_SIZE(out_obj) != nout) {
+ PyErr_SetString(PyExc_ValueError,
+ "The 'out' tuple must have exactly "
+ "one entry per ufunc output");
+ return -1;
+ }
+ if (tuple_all_none(out_obj)) {
+ return 0;
+ }
+ else {
+ Py_INCREF(out_obj);
+ full_args->out = out_obj;
+ }
+ }
+ else if (nout == 1) {
+ if (out_obj == Py_None) {
+ return 0;
+ }
+ /* Can be an array if it only has one output */
+ full_args->out = PyTuple_Pack(1, out_obj);
+ if (full_args->out == NULL) {
+ return -1;
+ }
+ }
+ else {
+ PyErr_SetString(PyExc_TypeError,
+ nout > 1 ? "'out' must be a tuple of arrays" :
+ "'out' must be an array or a tuple with "
+ "a single array");
+ return -1;
+ }
+ return 0;
+}
+
+
+/*
+ * Convert function which replaces np._NoValue with NULL.
+ * As a converter returns 0 on error and 1 on success.
+ */
+static int
+_not_NoValue(PyObject *obj, PyObject **out)
+{
+ static PyObject *NoValue = NULL;
+ npy_cache_import("numpy", "_NoValue", &NoValue);
+ if (NoValue == NULL) {
+ return 0;
+ }
+ if (obj == NoValue) {
+ *out = NULL;
+ }
+ else {
+ *out = obj;
+ }
+ return 1;
+}
+
/*
* This code handles reduce, reduceat, and accumulate
* (accumulate and reduce are special cases of the more general reduceat
* but they are handled separately for speed)
*/
static PyObject *
-PyUFunc_GenericReduction(PyUFuncObject *ufunc, PyObject *args,
- PyObject *kwds, int operation)
+PyUFunc_GenericReduction(PyUFuncObject *ufunc,
+ PyObject *const *args, Py_ssize_t len_args, PyObject *kwnames, int operation)
{
int i, naxes=0, ndim;
int axes[NPY_MAXDIMS];
- PyObject *axes_in = NULL;
+
+ ufunc_full_args full_args = {NULL, NULL};
+ PyObject *axes_obj = NULL;
PyArrayObject *mp = NULL, *wheremask = NULL, *ret = NULL;
- PyObject *op;
- PyObject *obj_ind;
+ PyObject *op = NULL;
PyArrayObject *indices = NULL;
PyArray_Descr *otype = NULL;
PyArrayObject *out = NULL;
int keepdims = 0;
PyObject *initial = NULL;
- static char *reduce_kwlist[] = {
- "array", "axis", "dtype", "out", "keepdims", "initial", "where", NULL};
- static char *accumulate_kwlist[] = {
- "array", "axis", "dtype", "out", NULL};
- static char *reduceat_kwlist[] = {
- "array", "indices", "axis", "dtype", "out", NULL};
+ npy_bool out_is_passed_by_position;
+
static char *_reduce_type[] = {"reduce", "accumulate", "reduceat", NULL};
@@ -4483,62 +4122,130 @@ PyUFunc_GenericReduction(PyUFuncObject *ufunc, PyObject *args,
_reduce_type[operation]);
return NULL;
}
- /* if there is a tuple of 1 for `out` in kwds, unpack it */
- if (kwds != NULL) {
- PyObject *out_obj = PyDict_GetItemWithError(kwds, npy_um_str_out);
- if (out_obj == NULL && PyErr_Occurred()){
- return NULL;
- }
- else if (out_obj != NULL && PyTuple_CheckExact(out_obj)) {
- if (PyTuple_GET_SIZE(out_obj) != 1) {
- PyErr_SetString(PyExc_ValueError,
- "The 'out' tuple must have exactly one entry");
- return NULL;
- }
- out_obj = PyTuple_GET_ITEM(out_obj, 0);
- PyDict_SetItem(kwds, npy_um_str_out, out_obj);
- }
- }
+ /*
+ * Perform argument parsing, but start by only extracting. This is
+ * just to preserve the behaviour that __array_ufunc__ did not perform
+ * any checks on arguments, and we could change this or change it for
+ * certain parameters.
+ */
+ PyObject *otype_obj = NULL, *out_obj = NULL, *indices_obj = NULL;
+ PyObject *keepdims_obj = NULL, *wheremask_obj = NULL;
if (operation == UFUNC_REDUCEAT) {
- PyArray_Descr *indtype;
- indtype = PyArray_DescrFromType(NPY_INTP);
- if (!PyArg_ParseTupleAndKeywords(args, kwds, "OO|OO&O&:reduceat", reduceat_kwlist,
- &op,
- &obj_ind,
- &axes_in,
- PyArray_DescrConverter2, &otype,
- PyArray_OutputConverter, &out)) {
+ NPY_PREPARE_ARGPARSER;
+
+ if (npy_parse_arguments("reduceat", args, len_args, kwnames,
+ "array", NULL, &op,
+ "indices", NULL, &indices_obj,
+ "|axis", NULL, &axes_obj,
+ "|dtype", NULL, &otype_obj,
+ "|out", NULL, &out_obj,
+ NULL, NULL, NULL) < 0) {
goto fail;
}
- indices = (PyArrayObject *)PyArray_FromAny(obj_ind, indtype,
- 1, 1, NPY_ARRAY_CARRAY, NULL);
- if (indices == NULL) {
+ /* Prepare inputs for PyUfunc_CheckOverride */
+ full_args.in = PyTuple_Pack(2, op, indices_obj);
+ if (full_args.in == NULL) {
goto fail;
}
+ out_is_passed_by_position = len_args >= 5;
}
else if (operation == UFUNC_ACCUMULATE) {
- if (!PyArg_ParseTupleAndKeywords(args, kwds, "O|OO&O&:accumulate",
- accumulate_kwlist,
- &op,
- &axes_in,
- PyArray_DescrConverter2, &otype,
- PyArray_OutputConverter, &out)) {
+ NPY_PREPARE_ARGPARSER;
+
+ if (npy_parse_arguments("accumulate", args, len_args, kwnames,
+ "array", NULL, &op,
+ "|axis", NULL, &axes_obj,
+ "|dtype", NULL, &otype_obj,
+ "|out", NULL, &out_obj,
+ NULL, NULL, NULL) < 0) {
+ goto fail;
+ }
+ /* Prepare input for PyUfunc_CheckOverride */
+ full_args.in = PyTuple_Pack(1, op);
+ if (full_args.in == NULL) {
goto fail;
}
+ out_is_passed_by_position = len_args >= 4;
}
else {
- if (!PyArg_ParseTupleAndKeywords(args, kwds, "O|OO&O&iOO&:reduce",
- reduce_kwlist,
- &op,
- &axes_in,
- PyArray_DescrConverter2, &otype,
- PyArray_OutputConverter, &out,
- &keepdims, &initial,
- _wheremask_converter, &wheremask)) {
+ NPY_PREPARE_ARGPARSER;
+
+ if (npy_parse_arguments("reduce", args, len_args, kwnames,
+ "array", NULL, &op,
+ "|axis", NULL, &axes_obj,
+ "|dtype", NULL, &otype_obj,
+ "|out", NULL, &out_obj,
+ "|keepdims", NULL, &keepdims_obj,
+ "|initial", &_not_NoValue, &initial,
+ "|where", NULL, &wheremask_obj,
+ NULL, NULL, NULL) < 0) {
+ goto fail;
+ }
+ /* Prepare input for PyUfunc_CheckOverride */
+ full_args.in = PyTuple_Pack(1, op);
+ if (full_args.in == NULL) {
+ goto fail;
+ }
+ out_is_passed_by_position = len_args >= 4;
+ }
+
+ /* Normalize output for PyUFunc_CheckOverride and conversion. */
+ if (out_is_passed_by_position) {
+ /* in this branch, out is always wrapped in a tuple. */
+ if (out_obj != Py_None) {
+ full_args.out = PyTuple_Pack(1, out_obj);
+ if (full_args.out == NULL) {
+ goto fail;
+ }
+ }
+ }
+ else if (out_obj) {
+ if (_set_full_args_out(1, out_obj, &full_args) < 0) {
+ goto fail;
+ }
+ /* Ensure that out_obj is the array, not the tuple: */
+ if (full_args.out != NULL) {
+ out_obj = PyTuple_GET_ITEM(full_args.out, 0);
+ }
+ }
+
+ /* We now have all the information required to check for Overrides */
+ PyObject *override = NULL;
+ int errval = PyUFunc_CheckOverride(ufunc, _reduce_type[operation],
+ full_args.in, full_args.out, args, len_args, kwnames, &override);
+ if (errval) {
+ return NULL;
+ }
+ else if (override) {
+ Py_XDECREF(full_args.in);
+ Py_XDECREF(full_args.out);
+ return override;
+ }
+
+ /* Finish parsing of all parameters (no matter which reduce-like) */
+ if (indices_obj) {
+ PyArray_Descr *indtype = PyArray_DescrFromType(NPY_INTP);
+
+ indices = (PyArrayObject *)PyArray_FromAny(indices_obj,
+ indtype, 1, 1, NPY_ARRAY_CARRAY, NULL);
+ if (indices == NULL) {
goto fail;
}
}
+ if (otype_obj && !PyArray_DescrConverter2(otype_obj, &otype)) {
+ goto fail;
+ }
+ if (out_obj && !PyArray_OutputConverter(out_obj, &out)) {
+ goto fail;
+ }
+ if (keepdims_obj && !PyArray_PythonPyIntFromInt(keepdims_obj, &keepdims)) {
+ goto fail;
+ }
+ if (wheremask_obj && !_wheremask_converter(wheremask_obj, &wheremask)) {
+ goto fail;
+ }
+
/* Ensure input is an array */
mp = (PyArrayObject *)PyArray_FromAny(op, NULL, 0, 0, 0, NULL);
if (mp == NULL) {
@@ -4557,7 +4264,7 @@ PyUFunc_GenericReduction(PyUFuncObject *ufunc, PyObject *args,
}
/* Convert the 'axis' parameter into a list of axes */
- if (axes_in == NULL) {
+ if (axes_obj == NULL) {
/* apply defaults */
if (ndim == 0) {
naxes = 0;
@@ -4567,22 +4274,22 @@ PyUFunc_GenericReduction(PyUFuncObject *ufunc, PyObject *args,
axes[0] = 0;
}
}
- else if (axes_in == Py_None) {
+ else if (axes_obj == Py_None) {
/* Convert 'None' into all the axes */
naxes = ndim;
for (i = 0; i < naxes; ++i) {
axes[i] = i;
}
}
- else if (PyTuple_Check(axes_in)) {
- naxes = PyTuple_Size(axes_in);
+ else if (PyTuple_Check(axes_obj)) {
+ naxes = PyTuple_Size(axes_obj);
if (naxes < 0 || naxes > NPY_MAXDIMS) {
PyErr_SetString(PyExc_ValueError,
"too many values for 'axis'");
goto fail;
}
for (i = 0; i < naxes; ++i) {
- PyObject *tmp = PyTuple_GET_ITEM(axes_in, i);
+ PyObject *tmp = PyTuple_GET_ITEM(axes_obj, i);
int axis = PyArray_PyIntAsInt(tmp);
if (error_converting(axis)) {
goto fail;
@@ -4595,7 +4302,7 @@ PyUFunc_GenericReduction(PyUFuncObject *ufunc, PyObject *args,
}
else {
/* Try to interpret axis as an integer */
- int axis = PyArray_PyIntAsInt(axes_in);
+ int axis = PyArray_PyIntAsInt(axes_obj);
/* TODO: PyNumber_Index would be good to use here */
if (error_converting(axis)) {
goto fail;
@@ -4686,6 +4393,8 @@ PyUFunc_GenericReduction(PyUFuncObject *ufunc, PyObject *args,
}
Py_DECREF(mp);
Py_DECREF(otype);
+ Py_XDECREF(full_args.in);
+ Py_XDECREF(full_args.out);
if (ret == NULL) {
return NULL;
@@ -4721,37 +4430,327 @@ fail:
Py_XDECREF(otype);
Py_XDECREF(mp);
Py_XDECREF(wheremask);
+ Py_XDECREF(full_args.in);
+ Py_XDECREF(full_args.out);
return NULL;
}
+/*
+ * Sets typetup to a new reference to the passed in dtype information
+ * tuple or NULL. Returns -1 on failure.
+ */
+static int
+_get_typetup(PyObject *sig_obj, PyObject *signature_obj, PyObject *dtype,
+ PyObject **out_typetup)
+{
+ *out_typetup = NULL;
+ if (signature_obj != NULL) {
+ Py_INCREF(signature_obj);
+ *out_typetup = signature_obj;
+ }
+
+ if (sig_obj != NULL) {
+ if (*out_typetup != NULL) {
+ PyErr_SetString(PyExc_TypeError,
+ "cannot specify both 'sig' and 'signature'");
+ Py_SETREF(*out_typetup, NULL);
+ return -1;
+ }
+ Py_INCREF(sig_obj);
+ *out_typetup = sig_obj;
+ }
+
+ if (dtype != NULL) {
+ if (*out_typetup != NULL) {
+ PyErr_SetString(PyExc_TypeError,
+ "cannot specify both 'signature' and 'dtype'");
+ Py_SETREF(*out_typetup, NULL);
+ return -1;
+ }
+ /* dtype needs to be converted, delay after the override check */
+ }
+ return 0;
+}
+
+/*
+ * Finish conversion parsing of the type tuple. This is currenlty only
+ * conversion of the `dtype` argument, but should do more in the future.
+ *
+ * TODO: The parsing of the typetup should be moved here (followup cleanup).
+ */
+static int
+_convert_typetup(PyObject *dtype_obj, PyObject **out_typetup)
+{
+ if (dtype_obj != NULL) {
+ PyArray_Descr *dtype = NULL;
+ if (!PyArray_DescrConverter2(dtype_obj, &dtype)) {
+ return -1;
+ }
+ if (dtype == NULL) {
+ /* dtype=None, no need to set typetup. */
+ return 0;
+ }
+ *out_typetup = PyTuple_Pack(1, (PyObject *)dtype);
+ Py_DECREF(dtype);
+ if (*out_typetup == NULL) {
+ return -1;
+ }
+ }
+ /* sig and signature are not converted here right now. */
+ return 0;
+}
+
+
+/*
+ * Main ufunc call implementation.
+ *
+ * This implementation makes use of the "fastcall" way of passing keyword
+ * arguments and is called directly from `ufunc_generic_vectorcall` when
+ * Python has `tp_vectorcall` (Python 3.8+).
+ * If `tp_vectorcall` is not available, the dictionary `kwargs` are unpacked in
+ * `ufunc_generic_call`/`ufunc_generic_call_with_operands` with fairly little
+ * overhead.
+ */
static PyObject *
-ufunc_generic_call(PyUFuncObject *ufunc, PyObject *args, PyObject *kwds)
+ufunc_generic_fastcall(PyUFuncObject *ufunc,
+ PyObject *const *args, Py_ssize_t len_args, PyObject *kwnames,
+ npy_bool outer, PyArrayObject **operands_in)
{
- int i;
- PyArrayObject *mps[NPY_MAXARGS];
+ PyArrayObject *operands_buffer[NPY_MAXARGS] = {NULL};
+ PyArrayObject **operands;
PyObject *retobj[NPY_MAXARGS];
PyObject *wraparr[NPY_MAXARGS];
PyObject *override = NULL;
ufunc_full_args full_args = {NULL, NULL};
+ PyObject *typetup = NULL;
+
int errval;
+ int nin = ufunc->nin, nout = ufunc->nout, nop = ufunc->nargs;
- errval = PyUFunc_CheckOverride(ufunc, "__call__", args, kwds, &override);
- if (errval) {
+ /*
+ * `PyUfunc_GenericFunction` uses `ufunc_generic_call_with_operands`
+ * which passes in the operands explicitly. `PyUfunc_GenericFunction`
+ * is deprecated and this can be simplified when the deprecation is over.
+ */
+ if (operands_in != NULL) {
+ operands = operands_in;
+ }
+ else {
+ operands = operands_buffer;
+ }
+
+ /*
+ * Note that the input (and possibly output) arguments are passed in as
+ * positional arguments. We extract these first and check for `out`
+ * passed by keyword later.
+ * Outputs and inputs are stored in `full_args.in` and `full_args.out`
+ * as tuples (or NULL when no outputs are passed).
+ */
+
+ /* Check number of arguments */
+ if ((len_args < nin) || (len_args > nop)) {
+ PyErr_Format(PyExc_TypeError,
+ "%s() takes from %d to %d positional arguments but "
+ "%zd were given",
+ ufunc_get_name_cstr(ufunc) , nin, nop, len_args);
return NULL;
}
- else if (override) {
- return override;
+
+ /* Fetch input arguments. */
+ full_args.in = PyTuple_New(ufunc->nin);
+ if (full_args.in == NULL) {
+ return NULL;
+ }
+ for (int i = 0; i < ufunc->nin; i++) {
+ PyObject *tmp = args[i];
+ Py_INCREF(tmp);
+ PyTuple_SET_ITEM(full_args.in, i, tmp);
+ }
+
+ /*
+ * If there are more arguments, they define the out args. Otherwise
+ * full_args.out is NULL for now, and the `out` kwarg may still be passed.
+ */
+ npy_bool out_is_passed_by_position = len_args > nin;
+ if (out_is_passed_by_position) {
+ npy_bool all_none = NPY_TRUE;
+
+ full_args.out = PyTuple_New(nout);
+ if (full_args.out == NULL) {
+ goto fail;
+ }
+ for (int i = nin; i < nop; i++) {
+ PyObject *tmp;
+ if (i < (int)len_args) {
+ tmp = args[i];
+ if (tmp != Py_None) {
+ all_none = NPY_FALSE;
+ }
+ }
+ else {
+ tmp = Py_None;
+ }
+ Py_INCREF(tmp);
+ PyTuple_SET_ITEM(full_args.out, i-nin, tmp);
+ }
+ if (all_none) {
+ Py_SETREF(full_args.out, NULL);
+ }
+ }
+ else {
+ full_args.out = NULL;
+ }
+
+ /*
+ * We have now extracted (but not converted) the input arguments.
+ * To simplify overrides, extract all other arguments (as objects only)
+ */
+ PyObject *out_obj = NULL, *where_obj = NULL;
+ PyObject *axes_obj = NULL, *axis_obj = NULL;
+ PyObject *keepdims_obj = NULL, *casting_obj = NULL, *order_obj = NULL;
+ PyObject *subok_obj = NULL, *signature_obj = NULL, *sig_obj = NULL;
+ PyObject *dtype_obj = NULL, *extobj = NULL;
+
+ /* Skip parsing if there are no keyword arguments, nothing left to do */
+ if (kwnames != NULL) {
+ if (!ufunc->core_enabled) {
+ NPY_PREPARE_ARGPARSER;
+
+ if (npy_parse_arguments(ufunc->name, args + len_args, 0, kwnames,
+ "$out", NULL, &out_obj,
+ "$where", NULL, &where_obj,
+ "$casting", NULL, &casting_obj,
+ "$order", NULL, &order_obj,
+ "$subok", NULL, &subok_obj,
+ "$dtype", NULL, &dtype_obj,
+ "$signature", NULL, &signature_obj,
+ "$sig", NULL, &sig_obj,
+ "$extobj", NULL, &extobj,
+ NULL, NULL, NULL) < 0) {
+ goto fail;
+ }
+ }
+ else {
+ NPY_PREPARE_ARGPARSER;
+
+ if (npy_parse_arguments(ufunc->name, args + len_args, 0, kwnames,
+ "$out", NULL, &out_obj,
+ "$axes", NULL, &axes_obj,
+ "$axis", NULL, &axis_obj,
+ "$keepdims", NULL, &keepdims_obj,
+ "$casting", NULL, &casting_obj,
+ "$order", NULL, &order_obj,
+ "$subok", NULL, &subok_obj,
+ "$dtype", NULL, &dtype_obj,
+ "$signature", NULL, &signature_obj,
+ "$sig", NULL, &sig_obj,
+ "$extobj", NULL, &extobj,
+ NULL, NULL, NULL) < 0) {
+ goto fail;
+ }
+ if (NPY_UNLIKELY((axes_obj != NULL) && (axis_obj != NULL))) {
+ PyErr_SetString(PyExc_TypeError,
+ "cannot specify both 'axis' and 'axes'");
+ goto fail;
+ }
+ }
+
+ /* Handle `out` arguments passed by keyword */
+ if (out_obj != NULL) {
+ if (out_is_passed_by_position) {
+ PyErr_SetString(PyExc_TypeError,
+ "cannot specify 'out' as both a "
+ "positional and keyword argument");
+ goto fail;
+ }
+ if (_set_full_args_out(nout, out_obj, &full_args) < 0) {
+ goto fail;
+ }
+ }
+
+ /* Only one of signature, sig, and dtype should be passed */
+ if (_get_typetup(sig_obj, signature_obj, dtype_obj, &typetup) < 0) {
+ goto fail;
+ }
+ }
+
+ char *method;
+ if (!outer) {
+ method = "__call__";
+ }
+ else {
+ method = "outer";
+ }
+ /* We now have all the information required to check for Overrides */
+ if (operands_in == NULL) {
+ /* Deprecated PyUfunc_GenericFunction path does not use overrides */
+ errval = PyUFunc_CheckOverride(ufunc, method,
+ full_args.in, full_args.out,
+ args, len_args, kwnames, &override);
+ if (errval) {
+ goto fail;
+ }
+ else if (override) {
+ Py_XDECREF(typetup);
+ Py_DECREF(full_args.in);
+ Py_XDECREF(full_args.out);
+ return override;
+ }
+ }
+
+ if (outer) {
+ /* Outer uses special preparation of inputs (expand dims) */
+ PyObject *new_in = prepare_input_arguments_for_outer(full_args.in, ufunc);
+ if (new_in == NULL) {
+ goto fail;
+ }
+ Py_SETREF(full_args.in, new_in);
+ }
+
+ /* Finish argument parsing/converting for the dtype and all others */
+ if (_convert_typetup(dtype_obj, &typetup) < 0) {
+ goto fail;
+ }
+
+ NPY_ORDER order = NPY_KEEPORDER;
+ NPY_CASTING casting = NPY_DEFAULT_ASSIGN_CASTING;
+ npy_bool subok = NPY_TRUE;
+ int keepdims = -1; /* We need to know if it was passed */
+ PyArrayObject *wheremask = NULL;
+ if (convert_ufunc_arguments(ufunc, full_args, operands,
+ order_obj, &order,
+ casting_obj, &casting,
+ subok_obj, &subok,
+ where_obj, &wheremask,
+ keepdims_obj, &keepdims) < 0) {
+ goto fail;
+ }
+
+ if (!ufunc->core_enabled) {
+ errval = PyUFunc_GenericFunctionInternal(ufunc, operands,
+ full_args, typetup, extobj, casting, order, subok,
+ wheremask);
+ Py_XDECREF(wheremask);
+ }
+ else {
+ errval = PyUFunc_GeneralizedFunctionInternal(ufunc, operands,
+ full_args, typetup, extobj, casting, order, subok,
+ axis_obj, axes_obj, keepdims);
}
- errval = PyUFunc_GenericFunction_int(ufunc, args, kwds, mps);
if (errval < 0) {
- return NULL;
+ goto fail;
+ }
+
+ if (operands_in != NULL) {
+ /* Deprecated PyUfunc_GenericFunction path does not wrap. */
+ Py_RETURN_NONE;
}
/* Free the input references */
- for (i = 0; i < ufunc->nin; i++) {
- Py_XDECREF(mps[i]);
+ for (int i = 0; i < ufunc->nin; i++) {
+ Py_XSETREF(operands[i], NULL);
}
/*
@@ -4771,15 +4770,10 @@ ufunc_generic_call(PyUFuncObject *ufunc, PyObject *args, PyObject *kwds)
* None --- array-object passed in don't call PyArray_Return
* method --- the __array_wrap__ method to call.
*/
- if (make_full_arg_tuple(&full_args, ufunc->nin, ufunc->nout, args, kwds) < 0) {
- goto fail;
- }
- if (_find_array_wrap(full_args, kwds, wraparr, ufunc->nin, ufunc->nout) < 0) {
- goto fail;
- }
+ _find_array_wrap(full_args, subok, wraparr, ufunc->nin, ufunc->nout);
/* wrap outputs */
- for (i = 0; i < ufunc->nout; i++) {
+ for (int i = 0; i < ufunc->nout; i++) {
int j = ufunc->nin+i;
_ufunc_context context;
PyObject *wrapped;
@@ -4788,10 +4782,10 @@ ufunc_generic_call(PyUFuncObject *ufunc, PyObject *args, PyObject *kwds)
context.args = full_args;
context.out_i = i;
- wrapped = _apply_array_wrap(wraparr[i], mps[j], &context);
- mps[j] = NULL; /* Prevent fail double-freeing this */
+ wrapped = _apply_array_wrap(wraparr[i], operands[j], &context);
+ operands[j] = NULL; /* Prevent fail double-freeing this */
if (wrapped == NULL) {
- for (j = 0; j < i; j++) {
+ for (int j = 0; j < i; j++) {
Py_DECREF(retobj[j]);
}
goto fail;
@@ -4800,6 +4794,7 @@ ufunc_generic_call(PyUFuncObject *ufunc, PyObject *args, PyObject *kwds)
retobj[i] = wrapped;
}
+ Py_XDECREF(typetup);
Py_XDECREF(full_args.in);
Py_XDECREF(full_args.out);
@@ -4810,21 +4805,115 @@ ufunc_generic_call(PyUFuncObject *ufunc, PyObject *args, PyObject *kwds)
PyTupleObject *ret;
ret = (PyTupleObject *)PyTuple_New(ufunc->nout);
- for (i = 0; i < ufunc->nout; i++) {
+ for (int i = 0; i < ufunc->nout; i++) {
PyTuple_SET_ITEM(ret, i, retobj[i]);
}
return (PyObject *)ret;
}
fail:
+ Py_XDECREF(typetup);
Py_XDECREF(full_args.in);
Py_XDECREF(full_args.out);
- for (i = ufunc->nin; i < ufunc->nargs; i++) {
- Py_XDECREF(mps[i]);
+ for (int i = 0; i < ufunc->nargs; i++) {
+ Py_XDECREF(operands[i]);
}
return NULL;
}
+
+/*
+ * TODO: The implementation below can be replaced with PyVectorcall_Call
+ * when available (should be Python 3.8+).
+ * TODO: After `PyUFunc_GenericFunction` is disabled `operands_in` becomes
+ * unnecessary and this function can be merged with `ufunc_generic_call`.
+ * The `operands_in` handling can also be removed entirely from
+ * `ufunc_generic_fastcall`.
+ */
+static PyObject *
+ufunc_generic_call_with_operands(
+ PyUFuncObject *ufunc, PyObject *args, PyObject *kwds,
+ PyArrayObject **operands_in)
+{
+ Py_ssize_t len_args = PyTuple_GET_SIZE(args);
+ /*
+ * Wrapper for tp_call to tp_fastcall, to support both on older versions
+ * of Python. (and generally simplifying support of both versions in the
+ * same codebase.
+ */
+ if (kwds == NULL) {
+ return ufunc_generic_fastcall(ufunc,
+ PySequence_Fast_ITEMS(args), len_args, NULL, NPY_FALSE,
+ operands_in);
+ }
+
+ PyObject *new_args[NPY_MAXARGS];
+ Py_ssize_t len_kwds = PyDict_Size(kwds);
+
+ if (NPY_UNLIKELY(len_args + len_kwds > NPY_MAXARGS)) {
+ /*
+ * We do not have enough scratch-space, so we have to abort;
+ * In practice this error should not be seen by users.
+ */
+ PyErr_Format(PyExc_ValueError,
+ "%s() takes from %d to %d positional arguments but "
+ "%zd were given",
+ ufunc_get_name_cstr(ufunc) , ufunc->nin, ufunc->nargs, len_args);
+ return NULL;
+ }
+
+ /* Copy args into the scratch space */
+ for (Py_ssize_t i = 0; i < len_args; i++) {
+ new_args[i] = PyTuple_GET_ITEM(args, i);
+ }
+
+ PyObject *kwnames = PyTuple_New(len_kwds);
+
+ PyObject *key, *value;
+ Py_ssize_t pos = 0;
+ Py_ssize_t i = 0;
+ while (PyDict_Next(kwds, &pos, &key, &value)) {
+ Py_INCREF(key);
+ PyTuple_SET_ITEM(kwnames, i, key);
+ new_args[i + len_args] = value;
+ i++;
+ }
+
+ PyObject *res = ufunc_generic_fastcall(ufunc,
+ new_args, len_args, kwnames, NPY_FALSE, operands_in);
+ Py_DECREF(kwnames);
+ return res;
+}
+
+
+static PyObject *
+ufunc_generic_call(PyUFuncObject *ufunc, PyObject *args, PyObject *kwds)
+{
+ return ufunc_generic_call_with_operands(ufunc, args, kwds, NULL);
+}
+
+
+#if PY_VERSION_HEX >= 0x03080000
+/*
+ * Implement vectorcallfunc which should be defined with Python 3.8+.
+ * In principle this could be backported, but the speed gain seems moderate
+ * since ufunc calls often do not have keyword arguments and always have
+ * a large overhead. The only user would potentially be cython probably.
+ */
+static PyObject *
+ufunc_generic_vectorcall(PyObject *ufunc,
+ PyObject *const *args, size_t len_args, PyObject *kwnames)
+{
+ /*
+ * Unlike METH_FASTCALL, `len_args` may have a flag to signal that
+ * args[-1] may be (temporarily) used. So normalize it here.
+ */
+ return ufunc_generic_fastcall((PyUFuncObject *)ufunc,
+ args, PyVectorcall_NARGS(len_args), kwnames, NPY_FALSE, NULL);
+}
+#endif /* PY_VERSION_HEX >= 0x03080000 */
+
+
NPY_NO_EXPORT PyObject *
ufunc_geterr(PyObject *NPY_UNUSED(dummy), PyObject *args)
{
@@ -4857,6 +4946,7 @@ ufunc_geterr(PyObject *NPY_UNUSED(dummy), PyObject *args)
return res;
}
+
NPY_NO_EXPORT PyObject *
ufunc_seterr(PyObject *NPY_UNUSED(dummy), PyObject *args)
{
@@ -4998,7 +5088,11 @@ PyUFunc_FromFuncAndDataAndSignatureAndIdentity(PyUFuncGenericFunction *func, voi
ufunc->core_dim_flags = NULL;
ufunc->userloops = NULL;
ufunc->ptr = NULL;
+#if PY_VERSION_HEX >= 0x03080000
+ ufunc->vectorcall = &ufunc_generic_vectorcall;
+#else
ufunc->reserved2 = NULL;
+#endif
ufunc->reserved1 = 0;
ufunc->iter_flags = 0;
@@ -5437,24 +5531,9 @@ ufunc_traverse(PyUFuncObject *self, visitproc visit, void *arg)
* The result has dimensions a.ndim + b.ndim
*/
static PyObject *
-ufunc_outer(PyUFuncObject *ufunc, PyObject *args, PyObject *kwds)
+ufunc_outer(PyUFuncObject *ufunc,
+ PyObject *const *args, Py_ssize_t len_args, PyObject *kwnames)
{
- int errval;
- PyObject *override = NULL;
- PyObject *ret;
- PyArrayObject *ap1 = NULL, *ap2 = NULL, *ap_new = NULL;
- PyObject *new_args, *tmp;
- static PyObject *_numpy_matrix;
-
-
- errval = PyUFunc_CheckOverride(ufunc, "outer", args, kwds, &override);
- if (errval) {
- return NULL;
- }
- else if (override) {
- return override;
- }
-
if (ufunc->core_enabled) {
PyErr_Format(PyExc_TypeError,
"method outer is not allowed in ufunc with non-trivial"\
@@ -5469,20 +5548,22 @@ ufunc_outer(PyUFuncObject *ufunc, PyObject *args, PyObject *kwds)
return NULL;
}
- if (PySequence_Length(args) != 2) {
+ if (len_args != 2) {
PyErr_SetString(PyExc_TypeError, "exactly two arguments expected");
return NULL;
}
- tmp = PySequence_GetItem(args, 0);
- if (tmp == NULL) {
- return NULL;
- }
+ return ufunc_generic_fastcall(ufunc, args, len_args, kwnames, NPY_TRUE, NULL);
+}
- npy_cache_import(
- "numpy",
- "matrix",
- &_numpy_matrix);
+
+static PyObject *
+prepare_input_arguments_for_outer(PyObject *args, PyUFuncObject *ufunc)
+{
+ PyArrayObject *ap1 = NULL;
+ PyObject *tmp;
+ static PyObject *_numpy_matrix;
+ npy_cache_import("numpy", "matrix", &_numpy_matrix);
const char *matrix_deprecation_msg = (
"%s.outer() was passed a numpy matrix as %s argument. "
@@ -5491,11 +5572,12 @@ ufunc_outer(PyUFuncObject *ufunc, PyObject *args, PyObject *kwds)
"array to retain the old behaviour. You can use `matrix.A` "
"to achieve this.");
+ tmp = PyTuple_GET_ITEM(args, 0);
+
if (PyObject_IsInstance(tmp, _numpy_matrix)) {
/* DEPRECATED 2020-05-13, NumPy 1.20 */
if (PyErr_WarnFormat(PyExc_DeprecationWarning, 1,
matrix_deprecation_msg, ufunc->name, "first") < 0) {
- Py_DECREF(tmp);
return NULL;
}
ap1 = (PyArrayObject *) PyArray_FromObject(tmp, NPY_NOTYPE, 0, 0);
@@ -5503,19 +5585,16 @@ ufunc_outer(PyUFuncObject *ufunc, PyObject *args, PyObject *kwds)
else {
ap1 = (PyArrayObject *) PyArray_FROM_O(tmp);
}
- Py_DECREF(tmp);
if (ap1 == NULL) {
return NULL;
}
- tmp = PySequence_GetItem(args, 1);
- if (tmp == NULL) {
- return NULL;
- }
+
+ PyArrayObject *ap2 = NULL;
+ tmp = PyTuple_GET_ITEM(args, 1);
if (PyObject_IsInstance(tmp, _numpy_matrix)) {
/* DEPRECATED 2020-05-13, NumPy 1.20 */
if (PyErr_WarnFormat(PyExc_DeprecationWarning, 1,
matrix_deprecation_msg, ufunc->name, "second") < 0) {
- Py_DECREF(tmp);
Py_DECREF(ap1);
return NULL;
}
@@ -5524,7 +5603,6 @@ ufunc_outer(PyUFuncObject *ufunc, PyObject *args, PyObject *kwds)
else {
ap2 = (PyArrayObject *) PyArray_FROM_O(tmp);
}
- Py_DECREF(tmp);
if (ap2 == NULL) {
Py_DECREF(ap1);
return NULL;
@@ -5550,6 +5628,7 @@ ufunc_outer(PyUFuncObject *ufunc, PyObject *args, PyObject *kwds)
newshape[i] = 1;
}
+ PyArrayObject *ap_new;
ap_new = (PyArrayObject *)PyArray_Newshape(ap1, &newdims, NPY_CORDER);
if (ap_new == NULL) {
goto fail;
@@ -5565,71 +5644,42 @@ ufunc_outer(PyUFuncObject *ufunc, PyObject *args, PyObject *kwds)
"To work around this issue, please convert the inputs to "
"numpy arrays.",
ufunc->name, Py_TYPE(ap_new)->tp_name);
+ Py_DECREF(ap_new);
goto fail;
}
- new_args = Py_BuildValue("(OO)", ap_new, ap2);
Py_DECREF(ap1);
- Py_DECREF(ap2);
- Py_DECREF(ap_new);
- ret = ufunc_generic_call(ufunc, new_args, kwds);
- Py_DECREF(new_args);
- return ret;
+ return Py_BuildValue("(NN)", ap_new, ap2);
fail:
Py_XDECREF(ap1);
Py_XDECREF(ap2);
- Py_XDECREF(ap_new);
return NULL;
}
static PyObject *
-ufunc_reduce(PyUFuncObject *ufunc, PyObject *args, PyObject *kwds)
+ufunc_reduce(PyUFuncObject *ufunc,
+ PyObject *const *args, Py_ssize_t len_args, PyObject *kwnames)
{
- int errval;
- PyObject *override = NULL;
-
- errval = PyUFunc_CheckOverride(ufunc, "reduce", args, kwds, &override);
- if (errval) {
- return NULL;
- }
- else if (override) {
- return override;
- }
- return PyUFunc_GenericReduction(ufunc, args, kwds, UFUNC_REDUCE);
+ return PyUFunc_GenericReduction(
+ ufunc, args, len_args, kwnames, UFUNC_REDUCE);
}
static PyObject *
-ufunc_accumulate(PyUFuncObject *ufunc, PyObject *args, PyObject *kwds)
+ufunc_accumulate(PyUFuncObject *ufunc,
+ PyObject *const *args, Py_ssize_t len_args, PyObject *kwnames)
{
- int errval;
- PyObject *override = NULL;
-
- errval = PyUFunc_CheckOverride(ufunc, "accumulate", args, kwds, &override);
- if (errval) {
- return NULL;
- }
- else if (override) {
- return override;
- }
- return PyUFunc_GenericReduction(ufunc, args, kwds, UFUNC_ACCUMULATE);
+ return PyUFunc_GenericReduction(
+ ufunc, args, len_args, kwnames, UFUNC_ACCUMULATE);
}
static PyObject *
-ufunc_reduceat(PyUFuncObject *ufunc, PyObject *args, PyObject *kwds)
+ufunc_reduceat(PyUFuncObject *ufunc,
+ PyObject *const *args, Py_ssize_t len_args, PyObject *kwnames)
{
- int errval;
- PyObject *override = NULL;
-
- errval = PyUFunc_CheckOverride(ufunc, "reduceat", args, kwds, &override);
- if (errval) {
- return NULL;
- }
- else if (override) {
- return override;
- }
- return PyUFunc_GenericReduction(ufunc, args, kwds, UFUNC_REDUCEAT);
+ return PyUFunc_GenericReduction(
+ ufunc, args, len_args, kwnames, UFUNC_REDUCEAT);
}
/* Helper for ufunc_at, below */
@@ -5686,14 +5736,6 @@ ufunc_at(PyUFuncObject *ufunc, PyObject *args)
char * err_msg = NULL;
NPY_BEGIN_THREADS_DEF;
- errval = PyUFunc_CheckOverride(ufunc, "at", args, NULL, &override);
- if (errval) {
- return NULL;
- }
- else if (override) {
- return override;
- }
-
if (ufunc->nin > 2) {
PyErr_SetString(PyExc_ValueError,
"Only unary and binary ufuncs supported at this time");
@@ -5715,6 +5757,15 @@ ufunc_at(PyUFuncObject *ufunc, PyObject *args)
"second operand needed for ufunc");
return NULL;
}
+ errval = PyUFunc_CheckOverride(ufunc, "at",
+ args, NULL, NULL, 0, NULL, &override);
+
+ if (errval) {
+ return NULL;
+ }
+ else if (override) {
+ return override;
+ }
if (!PyArray_Check(op1)) {
PyErr_SetString(PyExc_TypeError,
@@ -5967,16 +6018,16 @@ fail:
static struct PyMethodDef ufunc_methods[] = {
{"reduce",
(PyCFunction)ufunc_reduce,
- METH_VARARGS | METH_KEYWORDS, NULL },
+ METH_FASTCALL | METH_KEYWORDS, NULL },
{"accumulate",
(PyCFunction)ufunc_accumulate,
- METH_VARARGS | METH_KEYWORDS, NULL },
+ METH_FASTCALL | METH_KEYWORDS, NULL },
{"reduceat",
(PyCFunction)ufunc_reduceat,
- METH_VARARGS | METH_KEYWORDS, NULL },
+ METH_FASTCALL | METH_KEYWORDS, NULL },
{"outer",
(PyCFunction)ufunc_outer,
- METH_VARARGS | METH_KEYWORDS, NULL},
+ METH_FASTCALL | METH_KEYWORDS, NULL},
{"at",
(PyCFunction)ufunc_at,
METH_VARARGS, NULL},
@@ -6163,10 +6214,17 @@ NPY_NO_EXPORT PyTypeObject PyUFunc_Type = {
.tp_repr = (reprfunc)ufunc_repr,
.tp_call = (ternaryfunc)ufunc_generic_call,
.tp_str = (reprfunc)ufunc_repr,
- .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC,
+ .tp_flags = Py_TPFLAGS_DEFAULT |
+#if PY_VERSION_HEX >= 0x03080000
+ _Py_TPFLAGS_HAVE_VECTORCALL |
+#endif
+ Py_TPFLAGS_HAVE_GC,
.tp_traverse = (traverseproc)ufunc_traverse,
.tp_methods = ufunc_methods,
.tp_getset = ufunc_getset,
+#if PY_VERSION_HEX >= 0x03080000
+ .tp_vectorcall_offset = offsetof(PyUFuncObject, vectorcall),
+#endif
};
/* End of code for ufunc objects */
diff --git a/numpy/core/src/umath/ufunc_object.h b/numpy/core/src/umath/ufunc_object.h
index f5de9f9b7..6d4fed7c0 100644
--- a/numpy/core/src/umath/ufunc_object.h
+++ b/numpy/core/src/umath/ufunc_object.h
@@ -13,22 +13,8 @@ NPY_NO_EXPORT const char*
ufunc_get_name_cstr(PyUFuncObject *ufunc);
/* strings from umathmodule.c that are interned on umath import */
-NPY_VISIBILITY_HIDDEN extern PyObject *npy_um_str_out;
-NPY_VISIBILITY_HIDDEN extern PyObject *npy_um_str_where;
-NPY_VISIBILITY_HIDDEN extern PyObject *npy_um_str_axes;
-NPY_VISIBILITY_HIDDEN extern PyObject *npy_um_str_axis;
-NPY_VISIBILITY_HIDDEN extern PyObject *npy_um_str_keepdims;
-NPY_VISIBILITY_HIDDEN extern PyObject *npy_um_str_casting;
-NPY_VISIBILITY_HIDDEN extern PyObject *npy_um_str_order;
-NPY_VISIBILITY_HIDDEN extern PyObject *npy_um_str_dtype;
-NPY_VISIBILITY_HIDDEN extern PyObject *npy_um_str_subok;
-NPY_VISIBILITY_HIDDEN extern PyObject *npy_um_str_signature;
-NPY_VISIBILITY_HIDDEN extern PyObject *npy_um_str_sig;
-NPY_VISIBILITY_HIDDEN extern PyObject *npy_um_str_extobj;
NPY_VISIBILITY_HIDDEN extern PyObject *npy_um_str_array_prepare;
NPY_VISIBILITY_HIDDEN extern PyObject *npy_um_str_array_wrap;
-NPY_VISIBILITY_HIDDEN extern PyObject *npy_um_str_array_finalize;
-NPY_VISIBILITY_HIDDEN extern PyObject *npy_um_str_ufunc;
NPY_VISIBILITY_HIDDEN extern PyObject *npy_um_str_pyvals_name;
#endif
diff --git a/numpy/core/src/umath/umathmodule.c b/numpy/core/src/umath/umathmodule.c
index 474db0245..b4b7db760 100644
--- a/numpy/core/src/umath/umathmodule.c
+++ b/numpy/core/src/umath/umathmodule.c
@@ -216,44 +216,16 @@ add_newdoc_ufunc(PyObject *NPY_UNUSED(dummy), PyObject *args)
*****************************************************************************
*/
-NPY_VISIBILITY_HIDDEN PyObject *npy_um_str_out = NULL;
-NPY_VISIBILITY_HIDDEN PyObject *npy_um_str_where = NULL;
-NPY_VISIBILITY_HIDDEN PyObject *npy_um_str_axes = NULL;
-NPY_VISIBILITY_HIDDEN PyObject *npy_um_str_axis = NULL;
-NPY_VISIBILITY_HIDDEN PyObject *npy_um_str_keepdims = NULL;
-NPY_VISIBILITY_HIDDEN PyObject *npy_um_str_casting = NULL;
-NPY_VISIBILITY_HIDDEN PyObject *npy_um_str_order = NULL;
-NPY_VISIBILITY_HIDDEN PyObject *npy_um_str_dtype = NULL;
-NPY_VISIBILITY_HIDDEN PyObject *npy_um_str_subok = NULL;
-NPY_VISIBILITY_HIDDEN PyObject *npy_um_str_signature = NULL;
-NPY_VISIBILITY_HIDDEN PyObject *npy_um_str_sig = NULL;
-NPY_VISIBILITY_HIDDEN PyObject *npy_um_str_extobj = NULL;
NPY_VISIBILITY_HIDDEN PyObject *npy_um_str_array_prepare = NULL;
NPY_VISIBILITY_HIDDEN PyObject *npy_um_str_array_wrap = NULL;
-NPY_VISIBILITY_HIDDEN PyObject *npy_um_str_array_finalize = NULL;
-NPY_VISIBILITY_HIDDEN PyObject *npy_um_str_ufunc = NULL;
NPY_VISIBILITY_HIDDEN PyObject *npy_um_str_pyvals_name = NULL;
/* intern some strings used in ufuncs, returns 0 on success */
static int
intern_strings(void)
{
- if (!(npy_um_str_out = PyUnicode_InternFromString("out"))) return -1;
- if (!(npy_um_str_where = PyUnicode_InternFromString("where"))) return -1;
- if (!(npy_um_str_axes = PyUnicode_InternFromString("axes"))) return -1;
- if (!(npy_um_str_axis = PyUnicode_InternFromString("axis"))) return -1;
- if (!(npy_um_str_keepdims = PyUnicode_InternFromString("keepdims"))) return -1;
- if (!(npy_um_str_casting = PyUnicode_InternFromString("casting"))) return -1;
- if (!(npy_um_str_order = PyUnicode_InternFromString("order"))) return -1;
- if (!(npy_um_str_dtype = PyUnicode_InternFromString("dtype"))) return -1;
- if (!(npy_um_str_subok = PyUnicode_InternFromString("subok"))) return -1;
- if (!(npy_um_str_signature = PyUnicode_InternFromString("signature"))) return -1;
- if (!(npy_um_str_sig = PyUnicode_InternFromString("sig"))) return -1;
- if (!(npy_um_str_extobj = PyUnicode_InternFromString("extobj"))) return -1;
if (!(npy_um_str_array_prepare = PyUnicode_InternFromString("__array_prepare__"))) return -1;
if (!(npy_um_str_array_wrap = PyUnicode_InternFromString("__array_wrap__"))) return -1;
- if (!(npy_um_str_array_finalize = PyUnicode_InternFromString("__array_finalize__"))) return -1;
- if (!(npy_um_str_ufunc = PyUnicode_InternFromString("__array_ufunc__"))) return -1;
if (!(npy_um_str_pyvals_name = PyUnicode_InternFromString(UFUNC_PYVALS_NAME))) return -1;
return 0;
}
diff --git a/numpy/core/tests/test_ufunc.py b/numpy/core/tests/test_ufunc.py
index aa17d6b08..96bfe7c33 100644
--- a/numpy/core/tests/test_ufunc.py
+++ b/numpy/core/tests/test_ufunc.py
@@ -34,13 +34,13 @@ class TestUfuncKwargs:
assert_raises(TypeError, np.add, 1, 2, wherex=[True])
def test_sig_signature(self):
- assert_raises(ValueError, np.add, 1, 2, sig='ii->i',
+ assert_raises(TypeError, np.add, 1, 2, sig='ii->i',
signature='ii->i')
def test_sig_dtype(self):
- assert_raises(RuntimeError, np.add, 1, 2, sig='ii->i',
+ assert_raises(TypeError, np.add, 1, 2, sig='ii->i',
dtype=int)
- assert_raises(RuntimeError, np.add, 1, 2, signature='ii->i',
+ assert_raises(TypeError, np.add, 1, 2, signature='ii->i',
dtype=int)
def test_extobj_refcount(self):
diff --git a/numpy/core/tests/test_umath.py b/numpy/core/tests/test_umath.py
index 556856faf..f7d248068 100644
--- a/numpy/core/tests/test_umath.py
+++ b/numpy/core/tests/test_umath.py
@@ -99,9 +99,9 @@ class TestOut:
# Out argument must be tuple, since there are multiple outputs.
r1, r2 = np.frexp(d, out=o1, subok=subok)
- assert_raises(ValueError, np.add, a, 2, o, o, subok=subok)
- assert_raises(ValueError, np.add, a, 2, o, out=o, subok=subok)
- assert_raises(ValueError, np.add, a, 2, None, out=o, subok=subok)
+ assert_raises(TypeError, np.add, a, 2, o, o, subok=subok)
+ assert_raises(TypeError, np.add, a, 2, o, out=o, subok=subok)
+ assert_raises(TypeError, np.add, a, 2, None, out=o, subok=subok)
assert_raises(ValueError, np.add, a, 2, out=(o, o), subok=subok)
assert_raises(ValueError, np.add, a, 2, out=(), subok=subok)
assert_raises(TypeError, np.add, a, 2, [], subok=subok)
@@ -2364,12 +2364,14 @@ class TestSpecialMethods:
# __call__
a = A()
- res = np.multiply.__call__(1, a, foo='bar', answer=42)
+ with assert_raises(TypeError):
+ np.multiply.__call__(1, a, foo='bar', answer=42)
+ res = np.multiply.__call__(1, a, subok='bar', where=42)
assert_equal(res[0], a)
assert_equal(res[1], np.multiply)
assert_equal(res[2], '__call__')
assert_equal(res[3], (1, a))
- assert_equal(res[4], {'foo': 'bar', 'answer': 42})
+ assert_equal(res[4], {'subok': 'bar', 'where': 42})
# __call__, wrong args
assert_raises(TypeError, np.multiply, a)
@@ -2575,7 +2577,7 @@ class TestSpecialMethods:
assert_raises(TypeError, np.multiply, a, b, 'one', out='two')
assert_raises(TypeError, np.multiply, a, b, 'one', 'two')
assert_raises(ValueError, np.multiply, a, b, out=('one', 'two'))
- assert_raises(ValueError, np.multiply, a, out=())
+ assert_raises(TypeError, np.multiply, a, out=())
assert_raises(TypeError, np.modf, a, 'one', out=('two', 'three'))
assert_raises(TypeError, np.modf, a, 'one', 'two', 'three')
assert_raises(ValueError, np.modf, a, out=('one', 'two', 'three'))