diff options
author | Charles Harris <charlesr.harris@gmail.com> | 2021-03-24 15:26:55 -0600 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-03-24 15:26:55 -0600 |
commit | 74e5bff6e54cae165c088cd38104071469eb3d84 (patch) | |
tree | 7baf107b7a4efc523571872055f9d692435680d8 | |
parent | badbf70324274bdb4299d8c64d3d83a26be2d4c0 (diff) | |
parent | bebf5c891cb1c547b6de97ecd48af66853841028 (diff) | |
download | numpy-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.rst | 31 | ||||
-rw-r--r-- | numpy/core/include/numpy/ufuncobject.h | 4 | ||||
-rw-r--r-- | numpy/core/src/umath/override.c | 524 | ||||
-rw-r--r-- | numpy/core/src/umath/override.h | 7 | ||||
-rw-r--r-- | numpy/core/src/umath/ufunc_object.c | 1478 | ||||
-rw-r--r-- | numpy/core/src/umath/ufunc_object.h | 14 | ||||
-rw-r--r-- | numpy/core/src/umath/umathmodule.c | 28 | ||||
-rw-r--r-- | numpy/core/tests/test_ufunc.py | 6 | ||||
-rw-r--r-- | numpy/core/tests/test_umath.py | 14 |
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')) |