Josh
Josh

Reputation: 1305

How to Create Custom Error Messages in Python

How would I go about creating custom error messages in python without wrapping each statement in a try-except clause? For instance (assuming python3.6), assume I enter the following code in the python prompt:

the_variable=5
print(the_varible)

This will return:

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'the_varible' is not defined

Say I wanted to return an custom exception:

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'the_varible' is not defined. Did you mean 'the_variable'?

Of course, this an extremely contrived example, I could easily wrap this particular example in a try: except clause (e.g. like here, here and many others), but what I more generally want is to customize any error message of all statements in the program

For instance, if I create a file called my_script.py, which contains:

the_variable=5
print(the_varible)

and I run it with python my_script.py. I want to receive the same custom error message as above.

Upvotes: 0

Views: 2363

Answers (3)

TheOnlyPlaton
TheOnlyPlaton

Reputation: 1

The proper way to achieve a custom message in an error is to pass desired string to the error call.

Simple error raising:

raise NotImplementedError

Custom message with error raising:

raise NotImplementedError("Steve needs to do this!")

F-string error message error raising:

coder = "Steve"
raise NotImplementedError(f"{coder} needs to do this!")

Upvotes: 0

Barmar
Barmar

Reputation: 780655

You could catch all errors in your main() function, which is conventionally the starting function of an application.

def main():
    try:
        // all the rest of your code goes here
    except NameError as err:
        // your custom error handling goes here
    // other custom error handlers can go here

Upvotes: 2

Hiatsu
Hiatsu

Reputation: 157

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.

Upvotes: 0

Related Questions