summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEric Wieser <wieser.eric@gmail.com>2018-01-24 23:37:51 -0800
committerEric Wieser <wieser.eric@gmail.com>2018-04-22 12:40:12 -0700
commit40868df10690fe57f182064fb284ff6807a74dbb (patch)
treecf105f3c70b8ac15a4dd6afc5b1edd7ae3621a3d
parente0b5e8740efe6d42c909c1374494e614592c65ab (diff)
downloadnumpy-40868df10690fe57f182064fb284ff6807a74dbb.tar.gz
BUG: Calling convention of ufunc.__call__ should not affect __array_prepare__ or __array_wrap__ arguments
This fixes gh-10450. The effect here is that all of these will pass `args=(in1, in2)` to `__array_prepare__` and `__array_wrap__`: * `ufunc(in1, in2)` - unchanged * `ufunc(in1, in2, None)` - previously: * prepare: `(in1, in2, None)` * wrap: `(in1, in2, None)` * `ufunc(in1, in2, out=None)` - previously: * prepare: `(in1, in2, None)` * `ufunc(in1, in2, out=(None,))` - previously * prepare: `(in1, in2, (None,))` Whereas these will pass `args=(in1, in2, out)`; * `ufunc(in1, in2, out)` - unchanged * `ufunc(in1, in2, out=out)` - previously * wrap: `(in1, in2)` * `ufunc(in1, in2, out=(out,))` - previously * prepare: not called on out at all! * wrap: `(in1, in2)`
-rw-r--r--numpy/core/src/umath/ufunc_object.c356
-rw-r--r--numpy/core/tests/test_umath.py51
2 files changed, 262 insertions, 145 deletions
diff --git a/numpy/core/src/umath/ufunc_object.c b/numpy/core/src/umath/ufunc_object.c
index e0423630b..95daa2d2d 100644
--- a/numpy/core/src/umath/ufunc_object.c
+++ b/numpy/core/src/umath/ufunc_object.c
@@ -65,6 +65,28 @@
#endif
/**********************************************/
+typedef struct {
+ PyObject *in; /* The input arguments to the ufunc, a tuple */
+ PyObject *out; /* The output arguments, a tuple. If no non-None outputs are
+ provided, then this is NULL. */
+} ufunc_full_args;
+
+/* Get the arg tuple to pass in the context argument to __array_wrap__ and
+ * __array_prepare__.
+ *
+ * Output arguments are only passed if at least one is non-None.
+ */
+static PyObject *
+_get_wrap_prepare_args(ufunc_full_args full_args) {
+ if (full_args.out == NULL) {
+ Py_INCREF(full_args.in);
+ return full_args.in;
+ }
+ else {
+ return PySequence_Concat(full_args.in, full_args.out);
+ }
+}
+
/* ---------------------------------------------------------------- */
static int
@@ -132,7 +154,7 @@ PyUFunc_clearfperr()
* defines the method.
*/
static PyObject*
-_find_array_method(PyObject *args, int nin, PyObject *method_name)
+_find_array_method(PyObject *args, PyObject *method_name)
{
int i, n_methods;
PyObject *obj;
@@ -140,7 +162,7 @@ _find_array_method(PyObject *args, int nin, PyObject *method_name)
PyObject *method = NULL;
n_methods = 0;
- for (i = 0; i < nin; i++) {
+ for (i = 0; i < PyTuple_GET_SIZE(args); i++) {
obj = PyTuple_GET_ITEM(args, i);
if (PyArray_CheckExact(obj) || PyArray_IsAnyScalar(obj)) {
continue;
@@ -238,17 +260,17 @@ _get_output_array_method(PyObject *obj, PyObject *method,
* should just have PyArray_Return called.
*/
static void
-_find_array_prepare(PyObject *args, PyObject *kwds,
+_find_array_prepare(ufunc_full_args args,
PyObject **output_prep, int nin, int nout)
{
- Py_ssize_t nargs;
int i;
+ PyObject *prep;
/*
* Determine the prepping function given by the input arrays
* (could be NULL).
*/
- PyObject *prep = _find_array_method(args, nin, npy_um_str_array_prepare);
+ prep = _find_array_method(args.in, npy_um_str_array_prepare);
/*
* For all the output arrays decide what to do.
*
@@ -261,29 +283,16 @@ _find_array_prepare(PyObject *args, PyObject *kwds,
* exact ndarray so that no PyArray_Return is
* done in that case.
*/
- nargs = PyTuple_GET_SIZE(args);
- for (i = 0; i < nout; i++) {
- int j = nin + i;
- PyObject *obj = NULL;
- if (j < nargs) {
- obj = PyTuple_GET_ITEM(args, j);
- /* Output argument one may also be in a keyword argument */
- if (i == 0 && obj == Py_None && kwds != NULL) {
- obj = PyDict_GetItem(kwds, npy_um_str_out);
- }
- }
- /* Output argument one may also be in a keyword argument */
- else if (i == 0 && kwds != NULL) {
- obj = PyDict_GetItem(kwds, npy_um_str_out);
- }
-
- if (obj == NULL) {
+ if (args.out == NULL) {
+ for (i = 0; i < nout; i++) {
Py_XINCREF(prep);
output_prep[i] = prep;
}
- else {
+ }
+ else {
+ for (i = 0; i < nout; i++) {
output_prep[i] = _get_output_array_method(
- obj, npy_um_str_array_prepare, prep);
+ PyTuple_GET_ITEM(args.out, i), npy_um_str_array_prepare, prep);
}
}
Py_XDECREF(prep);
@@ -1141,22 +1150,31 @@ static int
prepare_ufunc_output(PyUFuncObject *ufunc,
PyArrayObject **op,
PyObject *arr_prep,
- PyObject *arr_prep_args,
+ ufunc_full_args full_args,
int i)
{
if (arr_prep != NULL && arr_prep != Py_None) {
PyObject *res;
PyArrayObject *arr;
+ PyObject *args_tup;
- res = PyObject_CallFunction(arr_prep, "O(OOi)",
- *op, ufunc, arr_prep_args, i);
- if ((res == NULL) || (res == Py_None) || !PyArray_Check(res)) {
- if (!PyErr_Occurred()){
- PyErr_SetString(PyExc_TypeError,
- "__array_prepare__ must return an "
- "ndarray or subclass thereof");
- }
- Py_XDECREF(res);
+ /* Call with the context argument */
+ args_tup = _get_wrap_prepare_args(full_args);
+ if (args_tup == NULL) {
+ return -1;
+ }
+ res = PyObject_CallFunction(
+ arr_prep, "O(OOi)", *op, ufunc, args_tup, i);
+ Py_DECREF(args_tup);
+
+ if (res == NULL) {
+ return -1;
+ }
+ else if (!PyArray_Check(res)) {
+ PyErr_SetString(PyExc_TypeError,
+ "__array_prepare__ must return an "
+ "ndarray or subclass thereof");
+ Py_DECREF(res);
return -1;
}
arr = (PyArrayObject *)res;
@@ -1199,7 +1217,7 @@ iterator_loop(PyUFuncObject *ufunc,
NPY_ORDER order,
npy_intp buffersize,
PyObject **arr_prep,
- PyObject *arr_prep_args,
+ ufunc_full_args full_args,
PyUFuncGenericFunction innerloop,
void *innerloopdata)
{
@@ -1261,7 +1279,7 @@ iterator_loop(PyUFuncObject *ufunc,
continue;
}
if (prepare_ufunc_output(ufunc, &op[nin+i],
- arr_prep[i], arr_prep_args, i) < 0) {
+ arr_prep[i], full_args, i) < 0) {
return -1;
}
}
@@ -1289,7 +1307,7 @@ iterator_loop(PyUFuncObject *ufunc,
/* Call the __array_prepare__ functions for the new array */
if (prepare_ufunc_output(ufunc, &op[nin+i],
- arr_prep[i], arr_prep_args, i) < 0) {
+ arr_prep[i], full_args, i) < 0) {
NpyIter_Close(iter);
NpyIter_Deallocate(iter);
return -1;
@@ -1369,7 +1387,7 @@ execute_legacy_ufunc_loop(PyUFuncObject *ufunc,
NPY_ORDER order,
npy_intp buffersize,
PyObject **arr_prep,
- PyObject *arr_prep_args)
+ ufunc_full_args full_args)
{
npy_intp nin = ufunc->nin, nout = ufunc->nout;
PyUFuncGenericFunction innerloop;
@@ -1406,7 +1424,7 @@ execute_legacy_ufunc_loop(PyUFuncObject *ufunc,
/* Call the __prepare_array__ if necessary */
if (prepare_ufunc_output(ufunc, &op[1],
- arr_prep[0], arr_prep_args, 0) < 0) {
+ arr_prep[0], full_args, 0) < 0) {
return -1;
}
@@ -1423,7 +1441,7 @@ execute_legacy_ufunc_loop(PyUFuncObject *ufunc,
/* Call the __prepare_array__ if necessary */
if (prepare_ufunc_output(ufunc, &op[1],
- arr_prep[0], arr_prep_args, 0) < 0) {
+ arr_prep[0], full_args, 0) < 0) {
return -1;
}
@@ -1465,7 +1483,7 @@ execute_legacy_ufunc_loop(PyUFuncObject *ufunc,
/* Call the __prepare_array__ if necessary */
if (prepare_ufunc_output(ufunc, &op[2],
- arr_prep[0], arr_prep_args, 0) < 0) {
+ arr_prep[0], full_args, 0) < 0) {
return -1;
}
@@ -1484,7 +1502,7 @@ execute_legacy_ufunc_loop(PyUFuncObject *ufunc,
/* Call the __prepare_array__ if necessary */
if (prepare_ufunc_output(ufunc, &op[2],
- arr_prep[0], arr_prep_args, 0) < 0) {
+ arr_prep[0], full_args, 0) < 0) {
return -1;
}
@@ -1503,7 +1521,7 @@ execute_legacy_ufunc_loop(PyUFuncObject *ufunc,
NPY_UF_DBG_PRINT("iterator loop\n");
if (iterator_loop(ufunc, op, dtypes, order,
- buffersize, arr_prep, arr_prep_args,
+ buffersize, arr_prep, full_args,
innerloop, innerloopdata) < 0) {
return -1;
}
@@ -1530,7 +1548,7 @@ execute_fancy_ufunc_loop(PyUFuncObject *ufunc,
NPY_ORDER order,
npy_intp buffersize,
PyObject **arr_prep,
- PyObject *arr_prep_args)
+ ufunc_full_args full_args)
{
int retval, i, nin = ufunc->nin, nout = ufunc->nout;
int nop = nin + nout;
@@ -1643,7 +1661,7 @@ execute_fancy_ufunc_loop(PyUFuncObject *ufunc,
Py_INCREF(op_tmp);
if (prepare_ufunc_output(ufunc, &op_tmp,
- arr_prep[i], arr_prep_args, i) < 0) {
+ arr_prep[i], full_args, i) < 0) {
NpyIter_Close(iter);
NpyIter_Deallocate(iter);
return -1;
@@ -1727,42 +1745,109 @@ execute_fancy_ufunc_loop(PyUFuncObject *ufunc,
return retval;
}
-static PyObject *
-make_arr_prep_args(npy_intp nin, PyObject *args, PyObject *kwds)
+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 = kwds ? PyDict_GetItem(kwds, npy_um_str_out) : NULL;
- PyObject *arr_prep_args;
+ PyObject *out_kwd = NULL;
+ npy_intp nargs = PyTuple_GET_SIZE(args);
+ npy_intp i;
- if (out == NULL) {
- Py_INCREF(args);
- return args;
+ /* 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;
}
- else {
- npy_intp i, nargs = PyTuple_GET_SIZE(args), n;
- n = nargs;
- if (n < nin + 1) {
- n = nin + 1;
- }
- arr_prep_args = PyTuple_New(n);
- if (arr_prep_args == NULL) {
- return NULL;
+
+ /* Look for output keyword arguments */
+ out_kwd = kwds ? PyDict_GetItem(kwds, npy_um_str_out) : NULL;
+
+ if (out_kwd != NULL) {
+ assert(nargs == nin);
+ if (out_kwd == Py_None) {
+ return 0;
}
- /* Copy the tuple, but set the nin-th item to the keyword arg */
- for (i = 0; i < nin; ++i) {
- PyObject *item = PyTuple_GET_ITEM(args, i);
- Py_INCREF(item);
- PyTuple_SET_ITEM(arr_prep_args, i, item);
+ 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;
}
- Py_INCREF(out);
- PyTuple_SET_ITEM(arr_prep_args, nin, out);
- for (i = nin+1; i < n; ++i) {
- PyObject *item = PyTuple_GET_ITEM(args, i);
- Py_INCREF(item);
- PyTuple_SET_ITEM(arr_prep_args, i, item);
+ 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;
}
+ }
+
+ /* 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);
+ }
- return arr_prep_args;
+ /* 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;
}
/*
@@ -2097,11 +2182,8 @@ PyUFunc_GeneralizedFunction(PyUFuncObject *ufunc,
int **remap_axis = 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.
- */
- PyObject *arr_prep_args = NULL;
+ /* 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 */
@@ -2300,19 +2382,15 @@ 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(args, kwds, arr_prep, nin, nout);
-
- /* Set up arr_prep_args if a prep function was needed */
- for (i = 0; i < nout; ++i) {
- if (arr_prep[i] != NULL && arr_prep[i] != Py_None) {
- arr_prep_args = make_arr_prep_args(nin, args, kwds);
- break;
- }
- }
+ _find_array_prepare(full_args, arr_prep, nin, nout);
}
/* If the loop wants the arrays, provide them */
@@ -2543,7 +2621,8 @@ PyUFunc_GeneralizedFunction(PyUFuncObject *ufunc,
Py_XDECREF(arr_prep[i]);
}
Py_XDECREF(type_tup);
- Py_XDECREF(arr_prep_args);
+ Py_XDECREF(full_args.in);
+ Py_XDECREF(full_args.out);
NPY_UF_DBG_PRINT("Returning Success\n");
@@ -2561,7 +2640,8 @@ fail:
Py_XDECREF(arr_prep[i]);
}
Py_XDECREF(type_tup);
- Py_XDECREF(arr_prep_args);
+ Py_XDECREF(full_args.in);
+ Py_XDECREF(full_args.out);
PyArray_free(remap_axis_memory);
PyArray_free(remap_axis);
return retval;
@@ -2599,7 +2679,7 @@ PyUFunc_GenericFunction(PyUFuncObject *ufunc,
* This is either args, or args with the out= parameter from
* kwds added appropriately.
*/
- PyObject *arr_prep_args = NULL;
+ ufunc_full_args full_args = {NULL, NULL};
int trivial_loop_ok = 0;
@@ -2691,19 +2771,14 @@ PyUFunc_GenericFunction(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(args, kwds, arr_prep, nin, nout);
-
- /* Set up arr_prep_args if a prep function was needed */
- for (i = 0; i < nout; ++i) {
- if (arr_prep[i] != NULL && arr_prep[i] != Py_None) {
- arr_prep_args = make_arr_prep_args(nin, args, kwds);
- break;
- }
- }
+ _find_array_prepare(full_args, arr_prep, nin, nout);
}
/* Start with the floating-point exception flags cleared */
@@ -2715,14 +2790,14 @@ PyUFunc_GenericFunction(PyUFuncObject *ufunc,
retval = execute_fancy_ufunc_loop(ufunc, wheremask,
op, dtypes, order,
- buffersize, arr_prep, arr_prep_args);
+ buffersize, arr_prep, full_args);
}
else {
NPY_UF_DBG_PRINT("Executing legacy inner loop\n");
retval = execute_legacy_ufunc_loop(ufunc, trivial_loop_ok,
op, dtypes, order,
- buffersize, arr_prep, arr_prep_args);
+ buffersize, arr_prep, full_args);
}
if (retval < 0) {
goto fail;
@@ -2742,7 +2817,8 @@ PyUFunc_GenericFunction(PyUFuncObject *ufunc,
Py_XDECREF(arr_prep[i]);
}
Py_XDECREF(type_tup);
- Py_XDECREF(arr_prep_args);
+ Py_XDECREF(full_args.in);
+ Py_XDECREF(full_args.out);
Py_XDECREF(wheremask);
NPY_UF_DBG_PRINT("Returning Success\n");
@@ -2758,7 +2834,8 @@ fail:
Py_XDECREF(arr_prep[i]);
}
Py_XDECREF(type_tup);
- Py_XDECREF(arr_prep_args);
+ Py_XDECREF(full_args.in);
+ Py_XDECREF(full_args.out);
Py_XDECREF(wheremask);
return retval;
@@ -4127,11 +4204,10 @@ fail:
* should just have PyArray_Return called.
*/
static void
-_find_array_wrap(PyObject *args, PyObject *kwds,
+_find_array_wrap(ufunc_full_args args, PyObject *kwds,
PyObject **output_wrap, int nin, int nout)
{
- Py_ssize_t nargs;
- int i, idx_offset, start_idx;
+ int i;
PyObject *obj;
PyObject *wrap = NULL;
@@ -4151,7 +4227,7 @@ _find_array_wrap(PyObject *args, PyObject *kwds,
* Determine the wrapping function given by the input arrays
* (could be NULL).
*/
- wrap = _find_array_method(args, nin, npy_um_str_array_wrap);
+ wrap = _find_array_method(args.in, npy_um_str_array_wrap);
/*
* For all the output arrays decide what to do.
@@ -4166,44 +4242,16 @@ _find_array_wrap(PyObject *args, PyObject *kwds,
* done in that case.
*/
handle_out:
- nargs = PyTuple_GET_SIZE(args);
- /* Default is using positional arguments */
- obj = args;
- idx_offset = nin;
- start_idx = 0;
- if (nin == nargs && kwds != NULL) {
- /* There may be a keyword argument we can use instead */
- obj = PyDict_GetItem(kwds, npy_um_str_out);
- if (obj == NULL) {
- /* No, go back to positional (even though there aren't any) */
- obj = args;
- }
- else {
- idx_offset = 0;
- if (PyTuple_Check(obj)) {
- /* If a tuple, must have all nout items */
- nargs = nout;
- }
- else {
- /* If the kwarg is not a tuple then it is an array (or None) */
- output_wrap[0] = _get_output_array_method(
- obj, npy_um_str_array_wrap, wrap);
- start_idx = 1;
- nargs = 1;
- }
+ if (args.out == NULL) {
+ for (i = 0; i < nout; i++) {
+ Py_XINCREF(wrap);
+ output_wrap[i] = wrap;
}
}
-
- for (i = start_idx; i < nout; ++i) {
- int j = idx_offset + i;
-
- if (j < nargs) {
+ else {
+ for (i = 0; i < nout; i++) {
output_wrap[i] = _get_output_array_method(
- PyTuple_GET_ITEM(obj, j), npy_um_str_array_wrap, wrap);
- }
- else {
- output_wrap[i] = wrap;
- Py_XINCREF(wrap);
+ PyTuple_GET_ITEM(args.out, i), npy_um_str_array_wrap, wrap);
}
}
@@ -4222,6 +4270,7 @@ ufunc_generic_call(PyUFuncObject *ufunc, PyObject *args, PyObject *kwds)
PyObject *wraparr[NPY_MAXARGS];
PyObject *res;
PyObject *override = NULL;
+ ufunc_full_args full_args = {NULL, NULL};
int errval;
errval = PyUFunc_CheckOverride(ufunc, "__call__", args, kwds, &override);
@@ -4286,7 +4335,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.
*/
- _find_array_wrap(args, kwds, wraparr, ufunc->nin, ufunc->nout);
+ if (make_full_arg_tuple(&full_args, ufunc->nin, ufunc->nout, args, kwds) < 0) {
+ goto fail;
+ }
+ _find_array_wrap(full_args, kwds, wraparr, ufunc->nin, ufunc->nout);
/* wrap outputs */
for (i = 0; i < ufunc->nout; i++) {
@@ -4294,12 +4346,22 @@ ufunc_generic_call(PyUFuncObject *ufunc, PyObject *args, PyObject *kwds)
PyObject *wrap = wraparr[i];
if (wrap != NULL) {
+ PyObject *args_tup;
if (wrap == Py_None) {
Py_DECREF(wrap);
retobj[i] = (PyObject *)mps[j];
continue;
}
- res = PyObject_CallFunction(wrap, "O(OOi)", mps[j], ufunc, args, i);
+
+ /* Call the method with appropriate context */
+ args_tup = _get_wrap_prepare_args(full_args);
+ if (args_tup == NULL) {
+ goto fail;
+ }
+ res = PyObject_CallFunction(
+ wrap, "O(OOi)", mps[j], ufunc, args_tup, i);
+ Py_DECREF(args_tup);
+
/* Handle __array_wrap__ that does not accept a context argument */
if (res == NULL && PyErr_ExceptionMatches(PyExc_TypeError)) {
PyErr_Clear();
@@ -4319,9 +4381,11 @@ ufunc_generic_call(PyUFuncObject *ufunc, PyObject *args, PyObject *kwds)
/* default behavior */
retobj[i] = PyArray_Return(mps[j]);
}
-
}
+ Py_XDECREF(full_args.in);
+ Py_XDECREF(full_args.out);
+
if (ufunc->nout == 1) {
return retobj[0];
}
@@ -4334,6 +4398,8 @@ ufunc_generic_call(PyUFuncObject *ufunc, PyObject *args, PyObject *kwds)
}
fail:
+ Py_XDECREF(full_args.in);
+ Py_XDECREF(full_args.out);
for (i = ufunc->nin; i < ufunc->nargs; i++) {
Py_XDECREF(mps[i]);
}
diff --git a/numpy/core/tests/test_umath.py b/numpy/core/tests/test_umath.py
index 9da6abd4b..ba567e68b 100644
--- a/numpy/core/tests/test_umath.py
+++ b/numpy/core/tests/test_umath.py
@@ -1413,6 +1413,57 @@ class TestSpecialMethods(object):
assert_equal(args[1], a)
assert_equal(i, 0)
+ def test_wrap_and_prepare_out(self):
+ # Calling convention for out should not affect how special methods are
+ # called
+
+ class StoreArrayPrepareWrap(np.ndarray):
+ _wrap_args = None
+ _prepare_args = None
+ def __new__(cls):
+ return np.empty(()).view(cls)
+ def __array_wrap__(self, obj, context):
+ self._wrap_args = context[1]
+ return obj
+ def __array_prepare__(self, obj, context):
+ self._prepare_args = context[1]
+ return obj
+ @property
+ def args(self):
+ # We need to ensure these are fetched at the same time, before
+ # any other ufuncs are calld by the assertions
+ return (self._prepare_args, self._wrap_args)
+ def __repr__(self):
+ return "a" # for short test output
+
+ def do_test(f_call, f_expected):
+ a = StoreArrayPrepareWrap()
+ f_call(a)
+ p, w = a.args
+ expected = f_expected(a)
+ try:
+ assert_equal(p, expected)
+ assert_equal(w, expected)
+ except AssertionError as e:
+ # assert_equal produces truly useless error messages
+ raise AssertionError("\n".join([
+ "Bad arguments passed in ufunc call",
+ " expected: {}".format(expected),
+ " __array_prepare__ got: {}".format(p),
+ " __array_wrap__ got: {}".format(w)
+ ]))
+
+ # method not on the out argument
+ do_test(lambda a: np.add(a, 0), lambda a: (a, 0))
+ do_test(lambda a: np.add(a, 0, None), lambda a: (a, 0))
+ do_test(lambda a: np.add(a, 0, out=None), lambda a: (a, 0))
+ do_test(lambda a: np.add(a, 0, out=(None,)), lambda a: (a, 0))
+
+ # method on the out argument
+ do_test(lambda a: np.add(0, 0, a), lambda a: (0, 0, a))
+ do_test(lambda a: np.add(0, 0, out=a), lambda a: (0, 0, a))
+ do_test(lambda a: np.add(0, 0, out=(a,)), lambda a: (0, 0, a))
+
def test_wrap_with_iterable(self):
# test fix for bug #1026: