I see lots of people handling exceptions the wrong way. Perhaps this applies to you too. Does the following situation sound familiar?
You’re writing some code, but you know that the library you’re using might raise an exception. You don’t remember which one, exactly. At this point, it’s tempting to use so-called catch-all blocks and get on with the fun stuff.
Table of contents
The worst way to do it
The worst you can do is create a try-except block that catches anything. By catch-all, I mean something like:
try: ... except: pass
Catch-all blocks like these are bad because:
- You have no idea what other exceptions might be raised (more on this later).
- We’re hiding the exception by silently using pass instead of logging the error.
Furthermore, an empty except will catch everything, including
KeyboardInterrupt (control + c),
SystemExit, and even
NameErrors! This means that the following code can not be stopped cleanly:
from time import sleep while True: try: print("Try and stop me") sleep(1) except: print("Don't.. stop.. me now!")
Feel free to try it. You need to close your terminal window or kill the Python process to stop this program.
A somewhat better way to catch all exceptions
In contrast, when using
except Exception, although still a quick and dirty way to catch too many exceptions, at least you’ll be able to stop the running process properly:
from time import sleep while True: try: print("Try and stop me") sleep(1) except Exception: print("Ok I'll stop!")
Exception you will not catch
KeyboardInterrupt and other such exceptions. Why’s that, you ask?
All exceptions inherit from a class called
BaseException. According to the official documentation: “In a
try statement with an
except clause that mentions a particular class, that clause also handles any exception classes derived from that class.” An empty
except is equivalent to
except BaseException, hence it will catch all possible exceptions.
In contrast, the class
Exception is defined as: “All built-in, non-system-exiting exceptions are derived from this class. All user-defined exceptions should also be derived from this class.”
It gets even worse
In the following example, we use the os library to get the current working directory. However, my fatty little fingers made a typo:
import os try: working_dir = os.getcdw() print(working_dir) except: print('error')
os.getcdw is not a function in the os module, a NameError is thrown. Instead of failing, the except clause will catch the error, print ‘error’ and the program will continue despite our blatant typo. Unfortunately, this one is not solvable by catching
Apparently, our little trick from step one is not a fix for all our problems. So what should we do?
Catch what you can handle
A phrase that is often heard about exceptions, is: catch what you can handle. Many developers are tempted to directly deal with exceptions, while it’s often better to let the exception propagate to a part of your program that can actually handle it.
For example, consider the part of a text editor that opens and loads files, let’s call it the
OpenFile class. If the user requested to open a file that does not exist, you can either directly handle that error, or let it propagate.
In this case, it’s better to propagate the exception to the caller, because
OpenFile has no idea how bad this exception is for the caller. The caller can handle the situation in multiple ways:
- It could create a new file with that name instead and go on
- Maybe the caller needs the file to be there, in which case it can show an error dialog to inform the user that this file does not exist.
Either way, it’s not up the
OpenFile class to decide what to do in case of a
So should an exception always be propagated? No. A possible exception that can be handled in the FileOpen class, is the
TimeoutError. You might want to retry a few times, for example, without bothering the caller with the error. This is an exception that
OpenFile can handle, so it’s OK to catch it and retry.
You should under no circumstance catch more exceptions than you can handle. Blanket except blocks are a recipe for bugs and unpredictable code. In other words: catch what you can handle.
If you write your code with the ‘catch what you can handle’ matra in mind, writing catch-all blocks is breaking all the rules. So please, stop doing it. As an exercise, you could revisit some of your existing code and see if it can be improved with this new knowledge!