diff options
author | Eric Wieser <wieser.eric@gmail.com> | 2018-01-24 23:37:51 -0800 |
---|---|---|
committer | Eric Wieser <wieser.eric@gmail.com> | 2018-04-22 12:40:12 -0700 |
commit | 40868df10690fe57f182064fb284ff6807a74dbb (patch) | |
tree | cf105f3c70b8ac15a4dd6afc5b1edd7ae3621a3d | |
parent | e0b5e8740efe6d42c909c1374494e614592c65ab (diff) | |
download | numpy-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.c | 356 | ||||
-rw-r--r-- | numpy/core/tests/test_umath.py | 51 |
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: |