summaryrefslogtreecommitdiff
path: root/numpy
diff options
context:
space:
mode:
authorMarten van Kerkwijk <mhvk@astro.utoronto.ca>2019-01-19 21:37:38 -0500
committerGitHub <noreply@github.com>2019-01-19 21:37:38 -0500
commit99953e494aa3be5ae8385d8461b9e4dfbeec43ec (patch)
treeff3f4872555fd520be2744e49e93d36421c1302c /numpy
parenta709c6e8535d40269de0551d45749133ea63f7ec (diff)
parent9c2296b242be4fe2fce46daf06a1e360ec267a08 (diff)
downloadnumpy-99953e494aa3be5ae8385d8461b9e4dfbeec43ec.tar.gz
Merge pull request #12593 from eric-wieser/ufunc-exceptions
ENH,WIP: Use richer exception types for ufunc type resolution errors
Diffstat (limited to 'numpy')
-rw-r--r--numpy/core/_exceptions.py100
-rw-r--r--numpy/core/src/umath/ufunc_type_resolution.c199
-rw-r--r--numpy/core/tests/test_multiarray.py2
3 files changed, 251 insertions, 50 deletions
diff --git a/numpy/core/_exceptions.py b/numpy/core/_exceptions.py
new file mode 100644
index 000000000..5e0105beb
--- /dev/null
+++ b/numpy/core/_exceptions.py
@@ -0,0 +1,100 @@
+"""
+Various richly-typed exceptions, that also help us deal with string formatting
+in python where it's easier.
+
+By putting the formatting in `__str__`, we also avoid paying the cost for
+users who silence the exceptions.
+"""
+from numpy.core.overrides import set_module
+
+
+def _unpack_tuple(tup):
+ if len(tup) == 1:
+ return tup[0]
+ else:
+ return tup
+
+
+def _display_as_base(cls):
+ """
+ A decorator that makes an exception class look like its base.
+
+ We use this to hide subclasses that are implementation details - the user
+ should catch the base type, which is what the traceback will show them.
+
+ Classes decorated with this decorator are subject to removal without a
+ deprecation warning.
+ """
+ assert issubclass(cls, Exception)
+ cls.__name__ = cls.__base__.__name__
+ cls.__qualname__ = cls.__base__.__qualname__
+ return cls
+
+
+class UFuncTypeError(TypeError):
+ """ Base class for all ufunc exceptions """
+ def __init__(self, ufunc):
+ self.ufunc = ufunc
+
+
+@_display_as_base
+class _UFuncNoLoopError(UFuncTypeError):
+ """ Thrown when a ufunc loop cannot be found """
+ def __init__(self, ufunc, dtypes):
+ super().__init__(ufunc)
+ self.dtypes = tuple(dtypes)
+
+ def __str__(self):
+ return (
+ "ufunc {!r} did not contain a loop with signature matching types "
+ "{!r} -> {!r}"
+ ).format(
+ self.ufunc.__name__,
+ _unpack_tuple(self.dtypes[:self.ufunc.nin]),
+ _unpack_tuple(self.dtypes[self.ufunc.nin:])
+ )
+
+
+@_display_as_base
+class _UFuncCastingError(UFuncTypeError):
+ def __init__(self, ufunc, casting, from_, to):
+ super().__init__(ufunc)
+ self.casting = casting
+ self.from_ = from_
+ self.to = to
+
+
+@_display_as_base
+class _UFuncInputCastingError(_UFuncCastingError):
+ """ Thrown when a ufunc input cannot be casted """
+ def __init__(self, ufunc, casting, from_, to, i):
+ super().__init__(ufunc, casting, from_, to)
+ self.in_i = i
+
+ def __str__(self):
+ # only show the number if more than one input exists
+ i_str = "{} ".format(self.in_i) if self.ufunc.nin != 1 else ""
+ return (
+ "Cannot cast ufunc {!r} input {}from {!r} to {!r} with casting "
+ "rule {!r}"
+ ).format(
+ self.ufunc.__name__, i_str, self.from_, self.to, self.casting
+ )
+
+
+@_display_as_base
+class _UFuncOutputCastingError(_UFuncCastingError):
+ """ Thrown when a ufunc output cannot be casted """
+ def __init__(self, ufunc, casting, from_, to, i):
+ super().__init__(ufunc, casting, from_, to)
+ self.out_i = i
+
+ def __str__(self):
+ # only show the number if more than one output exists
+ i_str = "{} ".format(self.out_i) if self.ufunc.nout != 1 else ""
+ return (
+ "Cannot cast ufunc {!r} output {}from {!r} to {!r} with casting "
+ "rule {!r}"
+ ).format(
+ self.ufunc.__name__, i_str, self.from_, self.to, self.casting
+ )
diff --git a/numpy/core/src/umath/ufunc_type_resolution.c b/numpy/core/src/umath/ufunc_type_resolution.c
index 3cf507258..e2f4d8005 100644
--- a/numpy/core/src/umath/ufunc_type_resolution.c
+++ b/numpy/core/src/umath/ufunc_type_resolution.c
@@ -16,6 +16,7 @@
#include "npy_config.h"
#include "npy_pycompat.h"
+#include "npy_import.h"
#include "numpy/ufuncobject.h"
#include "ufunc_type_resolution.h"
@@ -27,6 +28,26 @@
#include "cblasfuncs.h"
#endif
+static PyObject *
+npy_casting_to_py_object(NPY_CASTING casting)
+{
+ switch (casting) {
+ case NPY_NO_CASTING:
+ return PyUString_FromString("no");
+ case NPY_EQUIV_CASTING:
+ return PyUString_FromString("equiv");
+ case NPY_SAFE_CASTING:
+ return PyUString_FromString("safe");
+ case NPY_SAME_KIND_CASTING:
+ return PyUString_FromString("same_kind");
+ case NPY_UNSAFE_CASTING:
+ return PyUString_FromString("unsafe");
+ default:
+ return PyInt_FromLong(casting);
+ }
+}
+
+
static const char *
npy_casting_to_string(NPY_CASTING casting)
{
@@ -46,6 +67,9 @@ npy_casting_to_string(NPY_CASTING casting)
}
}
+/**
+ * Always returns -1 to indicate the exception was raised, for convenience
+ */
static int
raise_binary_type_reso_error(PyUFuncObject *ufunc, PyArrayObject **operands) {
PyObject *errmsg;
@@ -63,6 +87,126 @@ raise_binary_type_reso_error(PyUFuncObject *ufunc, PyArrayObject **operands) {
return -1;
}
+/** Helper function to raise UFuncNoLoopError
+ * Always returns -1 to indicate the exception was raised, for convenience
+ */
+static int
+raise_no_loop_found_error(
+ PyUFuncObject *ufunc, PyArray_Descr **dtypes, npy_intp n_dtypes)
+{
+ static PyObject *exc_type = NULL;
+ PyObject *exc_value;
+ PyObject *dtypes_tup;
+ npy_intp i;
+
+ npy_cache_import(
+ "numpy.core._exceptions", "_UFuncNoLoopError",
+ &exc_type);
+ if (exc_type == NULL) {
+ return -1;
+ }
+
+ /* convert dtypes to a tuple */
+ dtypes_tup = PyTuple_New(n_dtypes);
+ if (dtypes_tup == NULL) {
+ return -1;
+ }
+ for (i = 0; i < n_dtypes; ++i) {
+ Py_INCREF(dtypes[i]);
+ PyTuple_SET_ITEM(dtypes_tup, i, (PyObject *)dtypes[i]);
+ }
+
+ /* produce an error object */
+ exc_value = PyTuple_Pack(2, ufunc, dtypes_tup);
+ Py_DECREF(dtypes_tup);
+ if (exc_value == NULL){
+ return -1;
+ }
+ PyErr_SetObject(exc_type, exc_value);
+ Py_DECREF(exc_value);
+
+ return -1;
+}
+
+static int
+raise_casting_error(
+ PyObject *exc_type,
+ PyUFuncObject *ufunc,
+ NPY_CASTING casting,
+ PyArray_Descr *from,
+ PyArray_Descr *to,
+ npy_intp i)
+{
+ PyObject *exc_value;
+ PyObject *casting_value;
+
+ casting_value = npy_casting_to_py_object(casting);
+ if (casting_value == NULL) {
+ return -1;
+ }
+
+ exc_value = Py_BuildValue(
+ "ONOOi",
+ ufunc,
+ casting_value,
+ (PyObject *)from,
+ (PyObject *)to,
+ i
+ );
+ if (exc_value == NULL){
+ return -1;
+ }
+ PyErr_SetObject(exc_type, exc_value);
+ Py_DECREF(exc_value);
+
+ return -1;
+}
+
+/** Helper function to raise UFuncInputCastingError
+ * Always returns -1 to indicate the exception was raised, for convenience
+ */
+static int
+raise_input_casting_error(
+ PyUFuncObject *ufunc,
+ NPY_CASTING casting,
+ PyArray_Descr *from,
+ PyArray_Descr *to,
+ npy_intp i)
+{
+ static PyObject *exc_type = NULL;
+ npy_cache_import(
+ "numpy.core._exceptions", "_UFuncInputCastingError",
+ &exc_type);
+ if (exc_type == NULL) {
+ return -1;
+ }
+
+ return raise_casting_error(exc_type, ufunc, casting, from, to, i);
+}
+
+
+/** Helper function to raise UFuncOutputCastingError
+ * Always returns -1 to indicate the exception was raised, for convenience
+ */
+static int
+raise_output_casting_error(
+ PyUFuncObject *ufunc,
+ NPY_CASTING casting,
+ PyArray_Descr *from,
+ PyArray_Descr *to,
+ npy_intp i)
+{
+ static PyObject *exc_type = NULL;
+ npy_cache_import(
+ "numpy.core._exceptions", "_UFuncOutputCastingError",
+ &exc_type);
+ if (exc_type == NULL) {
+ return -1;
+ }
+
+ return raise_casting_error(exc_type, ufunc, casting, from, to, i);
+}
+
/*UFUNC_API
*
@@ -79,45 +223,18 @@ PyUFunc_ValidateCasting(PyUFuncObject *ufunc,
PyArray_Descr **dtypes)
{
int i, nin = ufunc->nin, nop = nin + ufunc->nout;
- const char *ufunc_name = ufunc_get_name_cstr(ufunc);
for (i = 0; i < nop; ++i) {
if (i < nin) {
if (!PyArray_CanCastArrayTo(operands[i], dtypes[i], casting)) {
- PyObject *errmsg;
- errmsg = PyUString_FromFormat("Cannot cast ufunc %s "
- "input from ", ufunc_name);
- PyUString_ConcatAndDel(&errmsg,
- PyObject_Repr((PyObject *)PyArray_DESCR(operands[i])));
- PyUString_ConcatAndDel(&errmsg,
- PyUString_FromString(" to "));
- PyUString_ConcatAndDel(&errmsg,
- PyObject_Repr((PyObject *)dtypes[i]));
- PyUString_ConcatAndDel(&errmsg,
- PyUString_FromFormat(" with casting rule %s",
- npy_casting_to_string(casting)));
- PyErr_SetObject(PyExc_TypeError, errmsg);
- Py_DECREF(errmsg);
- return -1;
+ return raise_input_casting_error(
+ ufunc, casting, PyArray_DESCR(operands[i]), dtypes[i], i);
}
} else if (operands[i] != NULL) {
if (!PyArray_CanCastTypeTo(dtypes[i],
PyArray_DESCR(operands[i]), casting)) {
- PyObject *errmsg;
- errmsg = PyUString_FromFormat("Cannot cast ufunc %s "
- "output from ", ufunc_name);
- PyUString_ConcatAndDel(&errmsg,
- PyObject_Repr((PyObject *)dtypes[i]));
- PyUString_ConcatAndDel(&errmsg,
- PyUString_FromString(" to "));
- PyUString_ConcatAndDel(&errmsg,
- PyObject_Repr((PyObject *)PyArray_DESCR(operands[i])));
- PyUString_ConcatAndDel(&errmsg,
- PyUString_FromFormat(" with casting rule %s",
- npy_casting_to_string(casting)));
- PyErr_SetObject(PyExc_TypeError, errmsg);
- Py_DECREF(errmsg);
- return -1;
+ return raise_output_casting_error(
+ ufunc, casting, dtypes[i], PyArray_DESCR(operands[i]), i);
}
}
}
@@ -1382,12 +1499,8 @@ PyUFunc_DefaultLegacyInnerLoopSelector(PyUFuncObject *ufunc,
{
int nargs = ufunc->nargs;
char *types;
- const char *ufunc_name;
- PyObject *errmsg;
int i, j;
- ufunc_name = ufunc_get_name_cstr(ufunc);
-
/*
* If there are user-loops search them first.
* TODO: There needs to be a loop selection acceleration structure,
@@ -1422,19 +1535,7 @@ PyUFunc_DefaultLegacyInnerLoopSelector(PyUFuncObject *ufunc,
types += nargs;
}
- errmsg = PyUString_FromFormat("ufunc '%s' did not contain a loop "
- "with signature matching types ", ufunc_name);
- for (i = 0; i < nargs; ++i) {
- PyUString_ConcatAndDel(&errmsg,
- PyObject_Repr((PyObject *)dtypes[i]));
- if (i < nargs - 1) {
- PyUString_ConcatAndDel(&errmsg, PyUString_FromString(" "));
- }
- }
- PyErr_SetObject(PyExc_TypeError, errmsg);
- Py_DECREF(errmsg);
-
- return -1;
+ return raise_no_loop_found_error(ufunc, dtypes, nargs);
}
typedef struct {
@@ -2251,7 +2352,7 @@ type_tuple_type_resolver(PyUFuncObject *self,
/* If no function was found, throw an error */
PyErr_Format(PyExc_TypeError,
- "No loop matching the specified signature and casting\n"
+ "No loop matching the specified signature and casting "
"was found for ufunc %s", ufunc_name);
return -1;
diff --git a/numpy/core/tests/test_multiarray.py b/numpy/core/tests/test_multiarray.py
index 3dd45c5ce..a67a29102 100644
--- a/numpy/core/tests/test_multiarray.py
+++ b/numpy/core/tests/test_multiarray.py
@@ -5920,7 +5920,7 @@ class TestMatmul(MatmulCommon):
assert_array_equal(out, tgt, err_msg=msg)
# test out with not allowed type cast (safe casting)
- msg = "Cannot cast ufunc matmul output"
+ msg = "Cannot cast ufunc .* output"
out = np.zeros((5, 2), dtype=np.int32)
assert_raises_regex(TypeError, msg, self.matmul, a, b, out=out)