diff --git a/Doc/whatsnew/3.8.rst b/Doc/whatsnew/3.8.rst index 4e6c8517375104..25b6c64abbc497 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/Include/pyerrors.h b/Include/pyerrors.h index f289471be20ccf..b86516a9076f48 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 2a9ec706467f3e..8130264365c2c6 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/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 00000000000000..30193bf5abef8a --- /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. diff --git a/Objects/exceptions.c b/Objects/exceptions.c index 2cce40f884442a..e27691118874bd 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 d18a284e9f9d22..9e42d51a361f86 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 15e6ba05714337..4534448f0c445c 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) {