diff options
| author | Marten van Kerkwijk <mhvk@astro.utoronto.ca> | 2019-01-19 21:37:38 -0500 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2019-01-19 21:37:38 -0500 |
| commit | 99953e494aa3be5ae8385d8461b9e4dfbeec43ec (patch) | |
| tree | ff3f4872555fd520be2744e49e93d36421c1302c /numpy | |
| parent | a709c6e8535d40269de0551d45749133ea63f7ec (diff) | |
| parent | 9c2296b242be4fe2fce46daf06a1e360ec267a08 (diff) | |
| download | numpy-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.py | 100 | ||||
| -rw-r--r-- | numpy/core/src/umath/ufunc_type_resolution.c | 199 | ||||
| -rw-r--r-- | numpy/core/tests/test_multiarray.py | 2 |
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) |
