summaryrefslogtreecommitdiff
path: root/numpy
diff options
context:
space:
mode:
authorMarten van Kerkwijk <mhvk@astro.utoronto.ca>2018-07-27 08:49:22 -0400
committerGitHub <noreply@github.com>2018-07-27 08:49:22 -0400
commit43bbeeff29b58a2ade71d7c6ab9c6b50abb45114 (patch)
tree5949f41cb9238967eb13de4a0ace8715417e10eb /numpy
parent0124839917feb4f124d07dbfbf79915f01624a3a (diff)
parente3b0a3f743cb215cf8a61e318687e9b54a8691a5 (diff)
downloadnumpy-43bbeeff29b58a2ade71d7c6ab9c6b50abb45114.tar.gz
Merge pull request #9022 from eric-wieser/reduce-hides-error
BUG: don't silence __array_wrap__ errors in ufunc.reduce
Diffstat (limited to 'numpy')
-rw-r--r--numpy/core/src/umath/ufunc_object.c296
-rw-r--r--numpy/core/tests/test_umath.py3
-rw-r--r--numpy/lib/tests/test_ufunclike.py3
3 files changed, 179 insertions, 123 deletions
diff --git a/numpy/core/src/umath/ufunc_object.c b/numpy/core/src/umath/ufunc_object.c
index a3fd72839..20c448d8b 100644
--- a/numpy/core/src/umath/ufunc_object.c
+++ b/numpy/core/src/umath/ufunc_object.c
@@ -71,6 +71,13 @@ typedef struct {
provided, then this is NULL. */
} ufunc_full_args;
+/* C representation of the context argument to __array_wrap__ */
+typedef struct {
+ PyUFuncObject *ufunc;
+ ufunc_full_args args;
+ int out_i;
+} _ufunc_context;
+
/* Get the arg tuple to pass in the context argument to __array_wrap__ and
* __array_prepare__.
*
@@ -303,6 +310,141 @@ _find_array_prepare(ufunc_full_args args,
}
+/*
+ * This function analyzes the input arguments
+ * and determines an appropriate __array_wrap__ function to call
+ * for the outputs.
+ *
+ * If an output argument is provided, then it is wrapped
+ * with its own __array_wrap__ not with the one determined by
+ * the input arguments.
+ *
+ * if the provided output argument is already an array,
+ * the wrapping function is None (which means no wrapping will
+ * be done --- not even PyArray_Return).
+ *
+ * A NULL is placed in output_wrap for outputs that
+ * should just have PyArray_Return called.
+ */
+static void
+_find_array_wrap(ufunc_full_args args, PyObject *kwds,
+ 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_GetItem(kwds,
+ npy_um_str_subok)) != NULL) {
+ if (obj != Py_True) {
+ /* skip search for wrap members */
+ goto handle_out;
+ }
+ }
+
+ /*
+ * Determine the wrapping function given by the input arrays
+ * (could be NULL).
+ */
+ wrap = _find_array_method(args.in, npy_um_str_array_wrap);
+
+ /*
+ * For all the output arrays decide what to do.
+ *
+ * 1) Use the wrap function determined from the input arrays
+ * This is the default if the output array is not
+ * passed in.
+ *
+ * 2) Use the __array_wrap__ method of the output object
+ * passed in. -- this is special cased for
+ * exact ndarray so that no PyArray_Return is
+ * done in that case.
+ */
+handle_out:
+ if (args.out == NULL) {
+ for (i = 0; i < nout; i++) {
+ Py_XINCREF(wrap);
+ output_wrap[i] = wrap;
+ }
+ }
+ else {
+ for (i = 0; i < nout; i++) {
+ output_wrap[i] = _get_output_array_method(
+ PyTuple_GET_ITEM(args.out, i), npy_um_str_array_wrap, wrap);
+ }
+ }
+
+ Py_XDECREF(wrap);
+ return;
+}
+
+
+/*
+ * Apply the __array_wrap__ function with the given array and content.
+ *
+ * Interprets wrap=None and wrap=NULL as intended by _find_array_wrap
+ *
+ * Steals a reference to obj and wrap.
+ * Pass context=NULL to indicate there is no context.
+ */
+static PyObject *
+_apply_array_wrap(
+ PyObject *wrap, PyArrayObject *obj, _ufunc_context const *context) {
+ if (wrap == NULL) {
+ /* default behavior */
+ return PyArray_Return(obj);
+ }
+ else if (wrap == Py_None) {
+ Py_DECREF(wrap);
+ return (PyObject *)obj;
+ }
+ else {
+ PyObject *res;
+ PyObject *py_context = NULL;
+
+ /* Convert the context object to a tuple, if present */
+ if (context == NULL) {
+ py_context = Py_None;
+ Py_INCREF(py_context);
+ }
+ else {
+ PyObject *args_tup;
+ /* Call the method with appropriate context */
+ args_tup = _get_wrap_prepare_args(context->args);
+ if (args_tup == NULL) {
+ goto fail;
+ }
+ py_context = Py_BuildValue("OOi",
+ context->ufunc, args_tup, context->out_i);
+ Py_DECREF(args_tup);
+ if (py_context == NULL) {
+ goto fail;
+ }
+ }
+ /* try __array_wrap__(obj, context) */
+ res = PyObject_CallFunctionObjArgs(wrap, obj, py_context, NULL);
+ Py_DECREF(py_context);
+
+ /* try __array_wrap__(obj) if the context argument is not accepted */
+ if (res == NULL && PyErr_ExceptionMatches(PyExc_TypeError)) {
+ PyErr_Clear();
+ res = PyObject_CallFunctionObjArgs(wrap, obj, NULL);
+ }
+ Py_DECREF(wrap);
+ Py_DECREF(obj);
+ return res;
+ fail:
+ Py_DECREF(wrap);
+ Py_DECREF(obj);
+ return NULL;
+ }
+}
+
+
/*UFUNC_API
*
* On return, if errobj is populated with a non-NULL value, the caller
@@ -4019,7 +4161,7 @@ PyUFunc_GenericReduction(PyUFuncObject *ufunc, PyObject *args,
int axes[NPY_MAXDIMS];
PyObject *axes_in = NULL;
PyArrayObject *mp = NULL, *ret = NULL;
- PyObject *op, *res = NULL;
+ PyObject *op;
PyObject *obj_ind, *context;
PyArrayObject *indices = NULL;
PyArray_Descr *otype = NULL;
@@ -4265,25 +4407,31 @@ PyUFunc_GenericReduction(PyUFuncObject *ufunc, PyObject *args,
return NULL;
}
- /* If an output parameter was provided, don't wrap it */
- if (out != NULL) {
- return (PyObject *)ret;
- }
-
- if (Py_TYPE(op) != Py_TYPE(ret)) {
- res = PyObject_CallMethod(op, "__array_wrap__", "O", ret);
- if (res == NULL) {
- PyErr_Clear();
- }
- else if (res == Py_None) {
- Py_DECREF(res);
+ /* Wrap and return the output */
+ {
+ /* Find __array_wrap__ - note that these rules are different to the
+ * normal ufunc path
+ */
+ PyObject *wrap;
+ if (out != NULL) {
+ wrap = Py_None;
+ Py_INCREF(wrap);
+ }
+ else if (Py_TYPE(op) != Py_TYPE(ret)) {
+ wrap = PyObject_GetAttr(op, npy_um_str_array_wrap);
+ if (wrap == NULL) {
+ PyErr_Clear();
+ }
+ else if (!PyCallable_Check(wrap)) {
+ Py_DECREF(wrap);
+ wrap = NULL;
+ }
}
else {
- Py_DECREF(ret);
- return res;
+ wrap = NULL;
}
+ return _apply_array_wrap(wrap, ret, NULL);
}
- return PyArray_Return(ret);
fail:
Py_XDECREF(otype);
@@ -4291,78 +4439,6 @@ fail:
return NULL;
}
-/*
- * This function analyzes the input arguments
- * and determines an appropriate __array_wrap__ function to call
- * for the outputs.
- *
- * If an output argument is provided, then it is wrapped
- * with its own __array_wrap__ not with the one determined by
- * the input arguments.
- *
- * if the provided output argument is already an array,
- * the wrapping function is None (which means no wrapping will
- * be done --- not even PyArray_Return).
- *
- * A NULL is placed in output_wrap for outputs that
- * should just have PyArray_Return called.
- */
-static void
-_find_array_wrap(ufunc_full_args args, PyObject *kwds,
- 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_GetItem(kwds,
- npy_um_str_subok)) != NULL) {
- if (obj != Py_True) {
- /* skip search for wrap members */
- goto handle_out;
- }
- }
-
- /*
- * Determine the wrapping function given by the input arrays
- * (could be NULL).
- */
- wrap = _find_array_method(args.in, npy_um_str_array_wrap);
-
- /*
- * For all the output arrays decide what to do.
- *
- * 1) Use the wrap function determined from the input arrays
- * This is the default if the output array is not
- * passed in.
- *
- * 2) Use the __array_wrap__ method of the output object
- * passed in. -- this is special cased for
- * exact ndarray so that no PyArray_Return is
- * done in that case.
- */
-handle_out:
- if (args.out == NULL) {
- for (i = 0; i < nout; i++) {
- Py_XINCREF(wrap);
- output_wrap[i] = wrap;
- }
- }
- else {
- for (i = 0; i < nout; i++) {
- output_wrap[i] = _get_output_array_method(
- PyTuple_GET_ITEM(args.out, i), npy_um_str_array_wrap, wrap);
- }
- }
-
- Py_XDECREF(wrap);
- return;
-}
-
static PyObject *
ufunc_generic_call(PyUFuncObject *ufunc, PyObject *args, PyObject *kwds)
@@ -4418,42 +4494,20 @@ ufunc_generic_call(PyUFuncObject *ufunc, PyObject *args, PyObject *kwds)
/* wrap outputs */
for (i = 0; i < ufunc->nout; i++) {
int j = ufunc->nin+i;
- PyObject *wrap = wraparr[i];
+ _ufunc_context context;
+ PyObject *wrapped;
- if (wrap == NULL) {
- /* default behavior */
- retobj[i] = PyArray_Return(mps[j]);
- }
- else if (wrap == Py_None) {
- Py_DECREF(wrap);
- retobj[i] = (PyObject *)mps[j];
- }
- else {
- PyObject *res;
- PyObject *args_tup;
+ context.ufunc = ufunc;
+ context.args = full_args;
+ context.out_i = 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();
- res = PyObject_CallFunctionObjArgs(wrap, mps[j], NULL);
- }
- Py_DECREF(wrap);
- Py_DECREF(mps[j]);
- mps[j] = NULL; /* Prevent fail double-freeing this */
- if (res == NULL) {
- goto fail;
- }
- retobj[i] = res;
+ wrapped = _apply_array_wrap(wraparr[i], mps[j], &context);
+ mps[j] = NULL; /* Prevent fail double-freeing this */
+ if (wrapped == NULL) {
+ goto fail;
}
+
+ retobj[i] = wrapped;
}
Py_XDECREF(full_args.in);
diff --git a/numpy/core/tests/test_umath.py b/numpy/core/tests/test_umath.py
index 95107b538..f98367688 100644
--- a/numpy/core/tests/test_umath.py
+++ b/numpy/core/tests/test_umath.py
@@ -1568,13 +1568,14 @@ class TestSpecialMethods(object):
class A(object):
def __array__(self):
- return np.zeros(1)
+ return np.zeros(2)
def __array_wrap__(self, arr, context):
raise RuntimeError
a = A()
assert_raises(RuntimeError, ncu.maximum, a, a)
+ assert_raises(RuntimeError, ncu.maximum.reduce, a)
def test_failing_out_wrap(self):
diff --git a/numpy/lib/tests/test_ufunclike.py b/numpy/lib/tests/test_ufunclike.py
index 5604b3744..361367b97 100644
--- a/numpy/lib/tests/test_ufunclike.py
+++ b/numpy/lib/tests/test_ufunclike.py
@@ -52,7 +52,8 @@ class TestUfunclike(object):
return res
def __array_wrap__(self, obj, context=None):
- obj.metadata = self.metadata
+ if isinstance(obj, MyArray):
+ obj.metadata = self.metadata
return obj
def __array_finalize__(self, obj):