NameError
is a builtin class, so nothing you do from Python can change how it works.
However, Python is open source, so you could change the source code to do what you want. You can get the code here.
From the Python source code, file Python/ceval.c
: (line numbers added for ease of finding things):
83> #define NAME_ERROR_MSG \
84> "name '%.200s' is not defined"
This is a C macro containing that error message. It is used six times later in this file, showing errors for deleting a local variable, deleting a global, twice in loading a local, and twice in loading a global.
2315> format_exc_check_arg(tstate, PyExc_NameError,
2316> NAME_ERROR_MSG,
2317> name);
In order to change the message to be context dependent (which is required for matching potential variable names), you could replace this macro with a function that analyzes the current scope, finds a variable with a similar name, and returns a new format string with the variable name embedded in it, e.g. "name '%.200s' is not defined. Did you mean 'the_variable'?"
Alternatively, you make the format string take two substrings and add another parameter to the format_exc_check_arg
function, and simply add a call to a function that only needs to find a similarly named variable as the final parameter. This would require changing about 10 call sites, including the six from earlier and a few from related exceptions, but all in the same way, so it shouldn't be too hard.
The code for format_exc_check_arg
is:
5400> static void
5401> format_exc_check_arg(PyThreadState *tstate, PyObject *exc,
5402> const char *format_str, PyObject *obj)
5403> {
5404> const char *obj_str;
5405>
5406> if (!obj)
5407> return;
5408>
5409> obj_str = PyUnicode_AsUTF8(obj);
5410> if (!obj_str)
5411> return;
5412>
5413> _PyErr_Format(tstate, exc, format_str, obj_str);
5414> }
This could be changed to
5400> static void
5401> format_exc_check_arg(PyThreadState *tstate, PyObject *exc,
5402> const char *format_str, PyObject *obj,
5403> PyObject* alt)
5404> {
5405> const char *obj_str;
5406> const char *alt_str;
5407>
5408> if (!obj)
5409> return;
5410>
5411> if (!alt)
5412> return;
5413>
5414> obj_str = PyUnicode_AsUTF8(obj);
5415> if (!obj_str)
5416> return;
5417>
5418> alt_str = PyUnicode_AsUTF8(alt);
5419> if (!alt_str)
5420> return;
5421>
5422> _PyErr_Format(tstate, exc, format_str, obj_str, alt_str);
5423> }
Now we have a call to _PyErr_Format
, which now also needs another parameter. This is in Python/errors.c
:
952> PyObject *
953> _PyErr_Format(PyThreadState *tstate, PyObject *exception,
954> const char *format, ...)
955> {
956> va_list vargs;
957> #ifdef HAVE_STDARG_PROTOTYPES
958> va_start(vargs, format);
959> #else
960> va_start(vargs);
961> #endif
962> _PyErr_FormatV(tstate, exception, format, vargs);
963> va_end(vargs);
964> return NULL;
965> }
And it already takes a variable number of arguments, so no changes are required.
Compile the modified code, add it to your path, and you have Python with a custom error message.
TL;DR: This can only be done by modifying the source code of Python itself.