6 Exceptionally Common Pitfalls of Python Exception Handling

There’s a little more to remember than try..except

Based on photo by Hugo Jehanne on Unsplash

1. Generic Error Catching

This one is extremely common to see, either due to developer laziness, or misunderstanding. A generic catch-all means that all python exceptions will be caught, not just those that inherit from Exception . The significance of this is that some types of exceptions you may not want to catch will still be caught, such as KeyboardInterrupt or other top-level exceptions. In some cases this can make it very difficult to kill a running script.

What you probably mean to do here is except Exception as e , which will catch most all exceptions that inherit from Exception . This includes most programming errors, such as KeyErrors , ValueError , and so on. However, even this isn’t really encouraged. Instead you should always try to narrow your exception handling down to be as specific as possible based on the code in your try block. When in doubt about inheritance, you can always reference this handy exception hierarchy.

2. Confusing Exception and BaseException

This issue is most commonly confronted when implementing your own custom exception types. While BaseException seems like the logical base class to build from based on its name, Exception is usually the better choice. This goes back to the previous point - BaseException includes things like SystemExit and KeyboardInterrupt . Due to the unexpected consequences listed above, you shouldn’t really ever inherit directly from BaseException . The python documentation also supports this rule.

The built-in exception classes can be subclassed to define new exceptions; programmers are encouraged to derive new exceptions from the Exception class or one of its subclasses, and not from BaseException.

3. Misunderstanding try..except..else..finally

The majority of python developers tend to stick with the traditional try..except code block for Exception handling, however python offers additional clauses that can be added when handling errors including additional except statements as well as else and finally .

Additional except blocks can be used if you wish to handle different types of exceptions differently.

else conditions can also be used in python error handling and must follow all except clauses. Any code in the else block will be triggered if the try block does NOT raise any exceptions.

The finally condition can be used in python error handling as well. Like its usage in other languages, the code in the finally block will be executed regardless following a successful exit from the try/else block, or any exception handling.

One thing to keep in mind when using finally is that whatever you return from finally will override anything returned within the try or else blocks.

4. Incorrect logging

Often times python developers have trouble logging exception details. How many times have you seen this.

Did you know that in the standard lib logging library, there is special handling for logging exceptions. If you use the exception() method from your logger instance it will automatically log your message in addition to the Exception stack trace.

You can also continue to use the regular logging level methods ( debug , info , warn , error ) and pass a parameter named exc_info set to True to include the stack trace.

Finally if you want to log or print an exception you should always use the repr of the exception rather than the str . The reason for this is that the str can be empty in some cases, which results in very unhelpful logs, while the repr will at least include the type of exception that occurred. When logging you can also access the contents of the exception by using e.args , which returns a tuple of strings.

5. Re-raising Exceptions

Raising exceptions while handing exceptions can be confusing. It’s pretty common to see code that looks like this, where the developer caught an exception, then wants to pass that exception forward or modify to a different exception type. Unfortunately, when handled this way, what really happens is that a new exception is raised and python believes that another exception was generated while trying to handle the original exception.

If you want to try to pass on an exception after handling, in effect re-raising it, you can just use the raise keyword on its own.

Finally, if you want to modify to a different exception type, but avoid losing the original exception context, you can use the from keyword.

6. NotImplemented vs NotImplementedError

In python when designing custom classes, developers are often confused whether they should use return NotImplemented or raise a NotImplementedError when addressing unimplemented methods in the class. The correct answer is it depends what you’re trying to do. NotImplemented is actually a system Singleton in python which is used to signal to the runtime that it should look elsewhere for the solution, whereas NotImplementedError is used to raise an exception when some piece of functionality is not available.

A good example of the difference here would be to return NotImplemented from a dunder method like __eq__ which would tell the runtime to look for a valid implementation elsewhere.

You might want to raise a NotImplementedError when defining an abstract class where the actual implementation of a method should be defined in a derived class.

Even through the two have similar names, they are not interchangeable. If you try to raise NotImplemented you’ll actually cause a TypeError .

Conclusion

Exception handling in python is an extremely important concept for beginner developers to grasp, but things can get a bit confusing when moving beyond the basics. Hopefully now you can avoid these 6 common pitfalls. 👍