Welcome to part 23 of the intermediate Python programming tutorial series. In this tutorial, we're going to cover how to handle for errors that occur in a headless/automated program that you're not constantly monitoring.
In our previous tutorial, we covered logging with Python, which is very useful, but, if you do hit errors and just return the str(e) of that error, you are likely to find it's not enough useful information, especially if your program is large. If it's a small program, you can probably find the variable it's talking about, like when you get a NameError: a is not defined
, maybe you only define and reference this a
once, so it's easy to immediately know where things went wrong. What if you get an error and have many references to a
? It might not be a NameError
either, it could be anything. With logging as we have it right now, we'd have no clue where to begin. What if your program is 100,000 lines long?! Yikes! So let's dig into how we can start to solve this.
Let's start with something simple, like:
try: a+b except Exception as e: print(str(e))
Here, we get name 'a' is not defined
. Luckily for us, we can actually use the sys
module to access more of the exception's information with sys.exc_info
, like so:
import sys try: a+b except: print(sys.exc_info()[0]) print(sys.exc_info()[1]) print(sys.exc_info()[2].tb_lineno) print('Error: {}. {}, line: {}'.format(sys.exc_info()[0], sys.exc_info()[1], sys.exc_info()[2].tb_lineno))
In this case, you will see:
<class 'NameError'> name 'a' is not defined 4 Error: <class 'NameError'>. name 'a' is not defined, line: 4
Notice that we've simply sliced the values of sys.exc_info(), and we didn't unpack them to variables. As per a StackOverflow comment I read, it would be unwise to unpack them, just in case you get an exception within the exception itself, and you would wind up with a circular reference that never gets garbage collected. Above my head, but apparently that's a thing.
Now, we can combine this with logging, and do something like:
import sys import logging def error_handling(): return 'Error: {}. {}, line: {}'.format(sys.exc_info()[0], sys.exc_info()[1], sys.exc_info()[2].tb_lineno) try: a+b except: logging.error(error_handling())
Output:
ERROR:root:Error: <class 'NameError'>. name 'a' is not defined, line: 9
Obviously, you can also configure logging to instead log this to a file. See the logging tutorial in this series for more information there.