From a8e31e82833e2c0ae80c2bae568b6da870c7f169 Mon Sep 17 00:00:00 2001 From: Sebastian Kreft Date: Tue, 27 Mar 2018 21:19:59 +0200 Subject: [PATCH 1/2] Add name attribute to NameError. This is the first change to implement PEP 473. The plan is to add the attributes described in the PEP one exeption at a time, adjusting the most common places where the exceptions are raised to also include the structured data. Once the exceptions have been updated, the next step would be to update the Python code and the remaining places where exceptions are raised in the C code. --- Include/pyerrors.h | 14 ++++ Lib/test/test_exceptions.py | 60 ++++++++++++++++ Objects/exceptions.c | 136 ++++++++++++++++++++++++++++++++++-- Python/ceval.c | 47 ++++++++----- Python/errors.c | 50 +++++++++++++ 5 files changed, 284 insertions(+), 23 deletions(-) diff --git a/Include/pyerrors.h b/Include/pyerrors.h index f289471be20ccf9..b86516a9076f484 100644 --- a/Include/pyerrors.h +++ b/Include/pyerrors.h @@ -65,6 +65,12 @@ typedef struct { PyObject *value; } PyStopIterationObject; +typedef struct { + PyException_HEAD + PyObject *msg; + PyObject *name; +} PyNameErrorObject; + /* Compatibility typedefs */ typedef PyOSErrorObject PyEnvironmentErrorObject; #ifdef MS_WINDOWS @@ -315,6 +321,14 @@ PyAPI_FUNC(PyObject *) PyErr_SetImportError(PyObject *, PyObject *, PyObject *); #endif +#if !defined(Py_LIMITED_API) || Py_LIMITED_API+0 >= 0x03060000 +PyAPI_FUNC(PyObject *) PyErr_SetNameErrorSubclass(PyObject *, PyObject *, + PyObject *); +#endif +#if !defined(Py_LIMITED_API) || Py_LIMITED_API+0 >= 0x03030000 +PyAPI_FUNC(PyObject *) PyErr_SetNameError(PyObject *, PyObject *); +#endif + /* Export the old function so that the existing API remains available: */ PyAPI_FUNC(void) PyErr_BadInternalCall(void); PyAPI_FUNC(void) _PyErr_BadInternalCall(const char *filename, int lineno); diff --git a/Lib/test/test_exceptions.py b/Lib/test/test_exceptions.py index 2a9ec706467f3ef..8130264365c2c6f 100644 --- a/Lib/test/test_exceptions.py +++ b/Lib/test/test_exceptions.py @@ -1338,5 +1338,65 @@ def test_copy_pickle(self): self.assertEqual(exc.path, orig.path) +class NameErrorTests(unittest.TestCase): + + def test_attributes(self): + # Setting 'name' should not be a problem. + exc = NameError('test') + self.assertIsNone(exc.name) + + exc = NameError('test', name='somemodule') + self.assertEqual(exc.name, 'somemodule') + + msg = "'invalid' is an invalid keyword argument for NameError" + with self.assertRaisesRegex(TypeError, msg): + NameError('test', invalid='keyword') + + with self.assertRaisesRegex(TypeError, msg): + NameError(invalid='keyword') + + def test_reset_attributes(self): + exc = NameError('test', name='name') + self.assertEqual(exc.args, ('test',)) + self.assertEqual(exc.msg, 'test') + self.assertEqual(exc.name, 'name') + + # Reset not specified attributes + exc.__init__() + self.assertEqual(exc.args, ()) + self.assertEqual(exc.msg, None) + self.assertEqual(exc.name, None) + + def test_non_str_argument(self): + # Issue #15778 + with check_warnings(('', BytesWarning), quiet=True): + arg = b'abc' + exc = NameError(arg) + self.assertEqual(str(arg), str(exc)) + + def test_copy_pickle(self): + for kwargs in (dict(), dict(name='somename')): + orig = NameError('test', **kwargs) + for proto in range(pickle.HIGHEST_PROTOCOL + 1): + exc = pickle.loads(pickle.dumps(orig, proto)) + self.assertEqual(exc.args, ('test',)) + self.assertEqual(exc.msg, 'test') + self.assertEqual(exc.name, orig.name) + for c in copy.copy, copy.deepcopy: + exc = c(orig) + self.assertEqual(exc.args, ('test',)) + self.assertEqual(exc.msg, 'test') + self.assertEqual(exc.name, orig.name) + + def test_name_is_set(self): + with self.assertRaises(NameError) as cm: + x = unbound_variable + self.assertEqual(cm.exception.name, 'unbound_variable') + + with self.assertRaises(NameError) as cm: + del unbound_variable + self.assertEqual(cm.exception.name, 'unbound_variable') + + if __name__ == '__main__': unittest.main() diff --git a/Objects/exceptions.c b/Objects/exceptions.c index 2cce40f884442ab..e27691118874bde 100644 --- a/Objects/exceptions.c +++ b/Objects/exceptions.c @@ -1289,14 +1289,142 @@ SimpleExtendsException(PyExc_RuntimeError, NotImplementedError, /* * NameError extends Exception */ -SimpleExtendsException(PyExc_Exception, NameError, - "Name not found globally."); +static int +NameError_init(PyNameErrorObject *self, PyObject *args, PyObject *kwds) +{ + static char *kwlist[] = {"name", 0}; + PyObject *empty_tuple; + PyObject *msg = NULL; + PyObject *name = NULL; + + if (BaseException_init((PyBaseExceptionObject *)self, args, NULL) == -1) + return -1; + + empty_tuple = PyTuple_New(0); + if (!empty_tuple) + return -1; + if (!PyArg_ParseTupleAndKeywords(empty_tuple, kwds, "|$O:NameError", kwlist, + &name)) { + Py_DECREF(empty_tuple); + return -1; + } + Py_DECREF(empty_tuple); + + Py_XINCREF(name); + Py_XSETREF(self->name, name); + + if (PyTuple_GET_SIZE(args) == 1) { + msg = PyTuple_GET_ITEM(args, 0); + Py_INCREF(msg); + } + Py_XSETREF(self->msg, msg); + + return 0; +} + +static int +NameError_clear(PyNameErrorObject *self) +{ + Py_CLEAR(self->msg); + Py_CLEAR(self->name); + return BaseException_clear((PyBaseExceptionObject *)self); +} + +static void +NameError_dealloc(PyNameErrorObject *self) +{ + _PyObject_GC_UNTRACK(self); + NameError_clear(self); + Py_TYPE(self)->tp_free((PyObject *)self); +} + +static int +NameError_traverse(PyNameErrorObject *self, visitproc visit, void *arg) +{ + Py_VISIT(self->msg); + Py_VISIT(self->name); + return BaseException_traverse((PyBaseExceptionObject *)self, visit, arg); +} + +static PyObject * +NameError_str(PyNameErrorObject *self) +{ + if (self->msg && PyUnicode_CheckExact(self->msg)) { + Py_INCREF(self->msg); + return self->msg; + } + else { + return BaseException_str((PyBaseExceptionObject *)self); + } +} + +static PyObject * +NameError_getstate(PyNameErrorObject *self) +{ + PyObject *dict = ((PyBaseExceptionObject *)self)->dict; + if (self->name) { + _Py_IDENTIFIER(name); + _Py_IDENTIFIER(path); + dict = dict ? PyDict_Copy(dict) : PyDict_New(); + if (dict == NULL) + return NULL; + if (self->name && _PyDict_SetItemId(dict, &PyId_name, self->name) < 0) { + Py_DECREF(dict); + return NULL; + } + return dict; + } + else if (dict) { + Py_INCREF(dict); + return dict; + } + else { + Py_RETURN_NONE; + } +} + +/* Pickling support */ +static PyObject * +NameError_reduce(PyNameErrorObject *self) +{ + PyObject *res; + PyObject *args; + PyObject *state = NameError_getstate(self); + if (state == NULL) + return NULL; + args = ((PyBaseExceptionObject *)self)->args; + if (state == Py_None) + res = PyTuple_Pack(2, Py_TYPE(self), args); + else + res = PyTuple_Pack(3, Py_TYPE(self), args, state); + Py_DECREF(state); + return res; +} + +static PyMemberDef NameError_members[] = { + {"msg", T_OBJECT, offsetof(PyNameErrorObject, msg), 0, + PyDoc_STR("exception message")}, + {"name", T_OBJECT, offsetof(PyNameErrorObject, name), 0, + PyDoc_STR("variable name")}, + {NULL} /* Sentinel */ +}; + +static PyMethodDef NameError_methods[] = { + {"__reduce__", (PyCFunction)NameError_reduce, METH_NOARGS}, + {NULL} +}; + +ComplexExtendsException(PyExc_Exception, NameError, + NameError, 0 /* new */, + NameError_methods, NameError_members, + 0 /* getset */, NameError_str, + "Name not found globally."); /* * UnboundLocalError extends NameError */ -SimpleExtendsException(PyExc_NameError, UnboundLocalError, - "Local name referenced but not bound to a value."); +MiddlingExtendsException(PyExc_NameError, UnboundLocalError, NameError, + "Local name referenced but not bound to a value."); /* * AttributeError extends Exception diff --git a/Python/ceval.c b/Python/ceval.c index d18a284e9f9d220..9e42d51a361f860 100644 --- a/Python/ceval.c +++ b/Python/ceval.c @@ -63,6 +63,7 @@ static PyObject * import_name(PyFrameObject *, PyObject *, PyObject *, static PyObject * import_from(PyObject *, PyObject *); static int import_all_from(PyObject *, PyObject *); static void format_exc_check_arg(PyObject *, const char *, PyObject *); +static void format_exc_name_error(PyObject *, const char *format_str, PyObject *name); static void format_exc_unbound(PyCodeObject *co, int oparg); static PyObject * unicode_concatenate(PyObject *, PyObject *, PyFrameObject *, const _Py_CODEUNIT *); @@ -71,11 +72,11 @@ static int check_args_iterable(PyObject *func, PyObject *vararg); static void format_kwargs_mapping_error(PyObject *func, PyObject *kwargs); #define NAME_ERROR_MSG \ - "name '%.200s' is not defined" + "name '%.200S' is not defined" #define UNBOUNDLOCAL_ERROR_MSG \ - "local variable '%.200s' referenced before assignment" + "local variable '%.200S' referenced before assignment" #define UNBOUNDFREE_ERROR_MSG \ - "free variable '%.200s' referenced before assignment" \ + "free variable '%.200S' referenced before assignment" \ " in enclosing scope" /* Dynamic execution profile */ @@ -1052,9 +1053,9 @@ _PyEval_EvalFrameDefault(PyFrameObject *f, int throwflag) TARGET(LOAD_FAST) { PyObject *value = GETLOCAL(oparg); if (value == NULL) { - format_exc_check_arg(PyExc_UnboundLocalError, - UNBOUNDLOCAL_ERROR_MSG, - PyTuple_GetItem(co->co_varnames, oparg)); + format_exc_name_error(PyExc_UnboundLocalError, + UNBOUNDLOCAL_ERROR_MSG, + PyTuple_GetItem(co->co_varnames, oparg)); goto error; } Py_INCREF(value); @@ -2025,9 +2026,9 @@ _PyEval_EvalFrameDefault(PyFrameObject *f, int throwflag) } err = PyObject_DelItem(ns, name); if (err != 0) { - format_exc_check_arg(PyExc_NameError, - NAME_ERROR_MSG, - name); + format_exc_name_error(PyExc_NameError, + NAME_ERROR_MSG, + name); goto error; } DISPATCH(); @@ -2120,7 +2121,7 @@ _PyEval_EvalFrameDefault(PyFrameObject *f, int throwflag) int err; err = PyDict_DelItem(f->f_globals, name); if (err != 0) { - format_exc_check_arg( + format_exc_name_error( PyExc_NameError, NAME_ERROR_MSG, name); goto error; } @@ -2155,7 +2156,7 @@ _PyEval_EvalFrameDefault(PyFrameObject *f, int throwflag) if (PyDict_CheckExact(f->f_builtins)) { v = PyDict_GetItem(f->f_builtins, name); if (v == NULL) { - format_exc_check_arg( + format_exc_name_error( PyExc_NameError, NAME_ERROR_MSG, name); goto error; @@ -2166,7 +2167,7 @@ _PyEval_EvalFrameDefault(PyFrameObject *f, int throwflag) v = PyObject_GetItem(f->f_builtins, name); if (v == NULL) { if (PyErr_ExceptionMatches(PyExc_KeyError)) - format_exc_check_arg( + format_exc_name_error( PyExc_NameError, NAME_ERROR_MSG, name); goto error; @@ -2191,8 +2192,8 @@ _PyEval_EvalFrameDefault(PyFrameObject *f, int throwflag) if (!_PyErr_OCCURRED()) { /* _PyDict_LoadGlobal() returns NULL without raising * an exception if the key doesn't exist */ - format_exc_check_arg(PyExc_NameError, - NAME_ERROR_MSG, name); + format_exc_name_error(PyExc_NameError, + NAME_ERROR_MSG, name); } goto error; } @@ -2212,7 +2213,7 @@ _PyEval_EvalFrameDefault(PyFrameObject *f, int throwflag) v = PyObject_GetItem(f->f_builtins, name); if (v == NULL) { if (PyErr_ExceptionMatches(PyExc_KeyError)) - format_exc_check_arg( + format_exc_name_error( PyExc_NameError, NAME_ERROR_MSG, name); goto error; @@ -2229,7 +2230,7 @@ _PyEval_EvalFrameDefault(PyFrameObject *f, int throwflag) SETLOCAL(oparg, NULL); DISPATCH(); } - format_exc_check_arg( + format_exc_name_error( PyExc_UnboundLocalError, UNBOUNDLOCAL_ERROR_MSG, PyTuple_GetItem(co->co_varnames, oparg) @@ -4963,6 +4964,14 @@ format_exc_check_arg(PyObject *exc, const char *format_str, PyObject *obj) PyErr_Format(exc, format_str, obj_str); } +static void +format_exc_name_error(PyObject *exc, const char *format_str, PyObject *name) +{ + PyObject* errmsg = PyUnicode_FromFormat(format_str, name); + PyErr_SetNameErrorSubclass(exc, errmsg, name); + Py_XDECREF(errmsg); +} + static void format_exc_unbound(PyCodeObject *co, int oparg) { @@ -4973,15 +4982,15 @@ format_exc_unbound(PyCodeObject *co, int oparg) if (oparg < PyTuple_GET_SIZE(co->co_cellvars)) { name = PyTuple_GET_ITEM(co->co_cellvars, oparg); - format_exc_check_arg( + format_exc_name_error( PyExc_UnboundLocalError, UNBOUNDLOCAL_ERROR_MSG, name); } else { name = PyTuple_GET_ITEM(co->co_freevars, oparg - PyTuple_GET_SIZE(co->co_cellvars)); - format_exc_check_arg(PyExc_NameError, - UNBOUNDFREE_ERROR_MSG, name); + format_exc_name_error(PyExc_NameError, + UNBOUNDFREE_ERROR_MSG, name); } } diff --git a/Python/errors.c b/Python/errors.c index 15e6ba057143371..4534448f0c445c8 100644 --- a/Python/errors.c +++ b/Python/errors.c @@ -801,6 +801,56 @@ PyErr_SetImportError(PyObject *msg, PyObject *name, PyObject *path) return PyErr_SetImportErrorSubclass(PyExc_ImportError, msg, name, path); } +PyObject * +PyErr_SetNameErrorSubclass(PyObject *exception, PyObject *msg, + PyObject *name) +{ + int issubclass; + PyObject *kwargs, *error; + + issubclass = PyObject_IsSubclass(exception, PyExc_NameError); + if (issubclass < 0) { + return NULL; + } + else if (!issubclass) { + PyErr_SetString(PyExc_TypeError, "expected a subclass of NameError"); + return NULL; + } + + if (msg == NULL) { + PyErr_SetString(PyExc_TypeError, "expected a message argument"); + return NULL; + } + + if (name == NULL) { + name = Py_None; + } + + kwargs = PyDict_New(); + if (kwargs == NULL) { + return NULL; + } + if (PyDict_SetItemString(kwargs, "name", name) < 0) { + goto done; + } + + error = _PyObject_FastCallDict(exception, &msg, 1, kwargs); + if (error != NULL) { + PyErr_SetObject((PyObject *)Py_TYPE(error), error); + Py_DECREF(error); + } + +done: + Py_DECREF(kwargs); + return NULL; +} + +PyObject * +PyErr_SetNameError(PyObject *msg, PyObject *name) +{ + return PyErr_SetNameErrorSubclass(PyExc_NameError, msg, name); +} + void _PyErr_BadInternalCall(const char *filename, int lineno) { From 9f17f9340c0fcaad60b6fc8dd7f1d6c5f92b9f38 Mon Sep 17 00:00:00 2001 From: Sebastian Kreft Date: Sun, 8 Apr 2018 18:31:22 +0200 Subject: [PATCH 2/2] Added News entry --- Doc/whatsnew/3.8.rst | 3 +++ .../Core and Builtins/2018-04-08-18-30-36.bpo-33159.C2FXjN.rst | 1 + 2 files changed, 4 insertions(+) create mode 100644 Misc/NEWS.d/next/Core and Builtins/2018-04-08-18-30-36.bpo-33159.C2FXjN.rst diff --git a/Doc/whatsnew/3.8.rst b/Doc/whatsnew/3.8.rst index 4e6c85173751040..25b6c64abbc4972 100644 --- a/Doc/whatsnew/3.8.rst +++ b/Doc/whatsnew/3.8.rst @@ -80,6 +80,9 @@ Other Language Changes * Added support of ``\N{name}`` escapes in :mod:`regular expressions `. (Contributed by Jonathan Eunice and Serhiy Storchaka in :issue:`30688`.) +* PEP 473 Support: the error ``NameError`` has now an optional ``name`` + attribute. + New Modules =========== diff --git a/Misc/NEWS.d/next/Core and Builtins/2018-04-08-18-30-36.bpo-33159.C2FXjN.rst b/Misc/NEWS.d/next/Core and Builtins/2018-04-08-18-30-36.bpo-33159.C2FXjN.rst new file mode 100644 index 000000000000000..30193bf5abef8a5 --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2018-04-08-18-30-36.bpo-33159.C2FXjN.rst @@ -0,0 +1 @@ +PEP 473 Support: ``NameError`` has now an optional ``name`` attribue.