5
\$\begingroup\$

Please can you review my standard exception handler for Python 3.x. It is located under the # +=+= line. The code above the line is to create an exception and so the code is code complete and runs standalone.

The code is just a standard exception formatter, so users are presented with easier to read messages rather than stack traces. This is a common idiom in many Python projects. But many / all don't format to the extent I have.

I have searched a lot, but I can't find anything better. I expected to find something on GitHub, but was unfruitful. If you find a way to improve this, please post it here so that I and others will know.

import inspect
import os
import sys
import traceback

if __name__ == '__main__':
    try:
        x = 42/0

    # +=+=+=+=+=+=+=+=+=+=+=+=+=
    except KeyboardInterrupt as e: # Ctrl-C
        raise e
    except SystemExit as e: # sys.exit()
        raise e
    except Exception as e :
        exc_type, exc_obj, exc_tb = sys.exc_info()
        scriptName = os.path.split(exc_tb.tb_frame.f_code.co_filename)[1]
        functionName = inspect.currentframe().f_code.co_name
        print('=======================================================================================================')
        print('Unhandled exception: ', exc_type, scriptName, 'at line', exc_tb.tb_lineno)
        print('                      in function', functionName)
        print('                     ', str(e))

        traceback.print_exc()
        sys.exit(1)
\$\endgroup\$
1
  • 3
    \$\begingroup\$ Having said that, I think you got a nice idea going and I'm totally incorporating your idea into my current project. \$\endgroup\$ – Mast Jan 7 at 9:25
7
\$\begingroup\$
  1. Rather than using as e then raise e you can just omit e.

    except KeyboardInterrupt as e:
        raise e
    
    except KeyboardInterrupt:
        raise
    
  2. Rather than specifying an except block for KeyboardInterrupt and SystemExit you can just specify them in the same except by passing a tuple.

    except KeyboardInterrupt:
        raise
    except SystemExit:
        raise
    
    except (KeyboardInterrupt, SystemExit):
        raise
    
  3. Both KeyboardInterrupt and SystemExit are not subclasses of Exception so you do not need to handle them. The exceptions will propagate like normal.

    You may want to familiarize yourself with the exception hierarchy.

  4. You can get all the information that sys.exc_info() gives from e.

    exc_type, exc_obj, exc_tb = sys.exc_info()
    
    exc_type, exc_obj, exc_tb = type(e), e, e.__traceback__
    
  5. You can get the current frame from exc_tb rather than inspect.currentframe(). This makes the code more portable if you ever decide to move the code into a function to format tracebacks.

    frame = inspect.currentframe()
    
    frame = exc_tb.tb_frame
    
  6. Please follow PEP 8 and name variables with snake_case. It is very easy to see the code you've taken from the Python docs and the code you've written yourself / taken from somewhere else.

  7. If you're going to hard code the == at least use * to build it the size you want.

    print('=======================================================================================================')
    
    print('=' * 103)
    
  8. Better yet just print to the size of the terminal.

    print('=' * os.get_terminal_size().columns)
    
  9. I would prefer to use f-strings if available.

    print('Unhandled exception: ', exc_type, script_name, 'at line', exc_tb.tb_lineno)
    
    print(f'Unhandled exception:  {exc_type} {script_name} at line {exc_tb.tb_lineno}')
    
if __name__ == '__main__':
    try:
        x = 42/0
    except Exception as e:
        exc_tb = e.__traceback__
        script_name = os.path.split(exc_tb.tb_frame.f_code.co_filename)[1]
        function_name = exc_tb.tb_frame.f_code.co_name
        print('=' * os.get_terminal_size().columns)
        print(f'Unhandled exception:  {type(e)} {script_name} at line {exc_tb.tb_lineno}')
        print(f'                      in function {function_name}')
        print(f'                      {e}')

        traceback.print_exc()
        raise SystemExit(1)
\$\endgroup\$
3
  • \$\begingroup\$ Wow! Some incredible feedback. I can see that I still have a lot to learn. I will hold off awarding the answer for a day or two, although I doubt that there will be a better answer. I still can't figure out why there isn't something like this on GitHub, maybe with the option to email the developer on exception (something for me to add. Maybe I will also add pprint.pprint(locals()) and pprint.pprint(globals())). \$\endgroup\$ – Mawg says reinstate Monica Jan 7 at 16:43
  • 2
    \$\begingroup\$ @MawgsaysreinstateMonica I would assume one of the following is why ① most authors assume the user will know how to read Python trackbacks ② many authors don't know how to format tracebacks - how many people know __traceback__ even exists never-mind tb_frame? ③ The application uses logging rather than exceptions to inform the user of errors and function state. ⏣ I'd assume a combination of ①&② leave many projects with simple except blocks if anything. Then because ③ is much easier and expressive than ② it's just not worth it to most people. That said pytest has something similar. \$\endgroup\$ – Peilonrayz Jan 7 at 16:52
  • 1
    \$\begingroup\$ Well, as you can guess, I am self-taught, in a rather piecemeal, patchwork fashion :-) I will look into logging and see how PyTest handles things. Thanks again for some great feedback \$\endgroup\$ – Mawg says reinstate Monica Jan 7 at 22:40

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.