Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions Doc/whatsnew/3.8.rst
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,9 @@ Other Language Changes
* Added support of ``\N{name}`` escapes in :mod:`regular expressions <re>`.
(Contributed by Jonathan Eunice and Serhiy Storchaka in :issue:`30688`.)

* PEP 473 Support: the error ``NameError`` has now an optional ``name``
attribute.


New Modules
===========
Expand Down
14 changes: 14 additions & 0 deletions Include/pyerrors.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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);
Expand Down
60 changes: 60 additions & 0 deletions Lib/test/test_exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
PEP 473 Support: ``NameError`` has now an optional ``name`` attribue.
136 changes: 132 additions & 4 deletions Objects/exceptions.c
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why msg and not message, which is the name for this property in python 2?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The structure of this exception was modeled after the current implementation of ImportError, which uses msg.

Note that the message attribute in exceptions have been deprecated since python 2.6 and has been removed in python 3. See PEP 352 for more details. I wonder though, why the msg attribute was added to ImportError.

That being said, I think that probably I just should remove the msg field.

Any thoughts?

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
Expand Down
47 changes: 28 additions & 19 deletions Python/ceval.c
Original file line number Diff line number Diff line change
Expand Up @@ -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 *);
Expand All @@ -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 */
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -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;
}
Expand All @@ -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;
Expand All @@ -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)
Expand Down Expand Up @@ -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)
{
Expand All @@ -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);
}
}

Expand Down
Loading