9 Things I Regret Not Knowing About Python Exceptions Sooner

1) Exception Hierarchy

Python has many built-in exceptions that we may encounter from time to time, such as ZeroDivisionError, KeyError, ValueError, TypeError, and so on.

Each exception is part of an exception hierarchy — this means that most exceptions inherit from the same parent class Exception in some way.

We can see this by printing the subclasses of certain exception classes using ._subclasses_().

9 Things I Regret Not Knowing About Python Exceptions Sooner

Another way is to check the parent class of an exception using .__bases__.

9 Things I Regret Not Knowing About Python Exceptions Sooner

This can be cumbersome, so I created a function to help you automate this:

9 Things I Regret Not Knowing About Python Exceptions Sooner

Some examples:

9 Things I Regret Not Knowing About Python Exceptions Sooner
9 Things I Regret Not Knowing About Python Exceptions Sooner

2) BaseException vs Exception

Above we saw Exception and BaseException, and Exception inherits from its parent class BaseException.

So what’s the difference?

  • Exception is the parent class of the most common exceptions we encounter in regular coding, such as ZeroDivision, ValueError, TypeError, KeyError, etc.
  • BaseException is used to distinguish from Exception, and other exceptions that inherit from BaseException are generally used for special cases.
  • Some subclasses of BaseException include KeyboardInterrupt, SystemExit, etc.

9 Things I Regret Not Knowing About Python Exceptions Sooner

When we create custom exceptions, we should almost always inherit from Exception rather than BaseException, because Exception indicates that it is a normal error caused by regular coding mistakes or issues.

On the other hand, BaseException includes other special exceptions like KeyboardInterrupt or SystemExit, which are forcefully raised when we want to exit a Python program.

Suppose we have a poorly written while loop that asks for user input:

9 Things I Regret Not Knowing About Python Exceptions Sooner

If we want to exit this while loop, we can press Control-C to force KeyboardInterrupt to terminate our Python program.

However, if we exclude BaseException as e, some strange things will happen.

9 Things I Regret Not Knowing About Python Exceptions Sooner

We can no longer use Control-C to force KeyboardInterrupt to terminate our program — we can only terminate the entire terminal instance now.

Importantly — excluding BaseException is bad practice, as it can lead to strange side effects in the program.

3) “except Exception as e” Should Be Last

Suppose each try block has multiple exceptions, and each except block handles different exceptions in some way.

9 Things I Regret Not Knowing About Python Exceptions Sooner

For sorting reasons, we first check if there is a ZeroDivisionError.

  • If there is, execute that except block.
  • If not, we continue to the next block.
  • The except Exception as e block captures all other previously uncaught exceptions, so it should be last.

If we put except Exception as e first, it will handle all exceptions, and the other except blocks will not execute.

9 Things I Regret Not Knowing About Python Exceptions Sooner

Note — ZeroDivisionError is a subclass of Exception. That’s why we need to put it before the except Exception as e block.

4) except (Exception1, Exception2) as e

But what if we want to handle a bunch of exceptions in the same way? Do we have to repeat code for each exception? For example:

9 Things I Regret Not Knowing About Python Exceptions Sooner

We don’t have to repeat so much code.

In the example above, the first three except blocks that handle exceptions in the same way can be compressed into one except block:

9 Things I Regret Not Knowing About Python Exceptions Sooner

Here, the first except block captures ZeroDivisionError, ValueError, or KeyError, and handles them in the same way.

5) Assert with Message

We typically use the assert keyword to check if a condition is met.

  • If the condition is True, nothing happens.
  • If the condition is False, an AssertionError occurs.

9 Things I Regret Not Knowing About Python Exceptions Sooner

This is a simple function example — we ask the user for input, but the only valid inputs are “A” or “B”. So we use an assert statement to ensure this.

9 Things I Regret Not Knowing About Python Exceptions Sooner

If we input anything other than A or B, we get an AssertionError:

9 Things I Regret Not Knowing About Python Exceptions Sooner

But did you know we can add a custom message to the AssertionError? We just need to add a string (separated by a comma) after the assertion message.

9 Things I Regret Not Knowing About Python Exceptions Sooner

When an AssertionError occurs, the assertion message will also be displayed.

9 Things I Regret Not Knowing About Python Exceptions Sooner

6) How to Ignore Assert Statements

There is a way to ignore all existing assert statements. That is to use the -O flag when running the Python script.

Suppose we have the following Python script with assert statements. We run it normally with the command python filename.py.

9 Things I Regret Not Knowing About Python Exceptions Sooner

Here, we just get an AssertionError and don’t reach our print statement at all.

However, we can use the -O flag to ignore assert statements. We now run this script with the command python -O filename.py.

9 Things I Regret Not Knowing About Python Exceptions Sooner

Note — this is a capital O.

When we run Python with the -O flag, the special variable debug becomes False (which is usually True by default). Therefore, all assert statements are ignored.

If we have many assert statements but want to run all the code without triggering any assert statements to raise an AssertionError, this trick is quite useful.

7) try except else Block

We may have learned about the try-except-finally block when learning the basics of Python. But have you heard of the try-except-else-finally block?

9 Things I Regret Not Knowing About Python Exceptions Sooner

The else block is located after the except block.

  • If an exception occurs, the except block runs, but the else block does not run.
  • If no exception occurs, the except block does not run, but the else block will run.

We force a ZeroDivisionError. In this case, since the except block runs, the else block does not run. Regardless, the finally block always runs.

9 Things I Regret Not Knowing About Python Exceptions Sooner

Now let’s run the try block without errors. Now that no exception occurs, our except block does not run. Therefore, our else block will run.

9 Things I Regret Not Knowing About Python Exceptions Sooner

8) finally Block Can Run After Return Statement

Normally, nothing happens after the return statement in a function. Once the return statement is executed, we immediately exit the function.

Let’s write a simple function to illustrate this:

9 Things I Regret Not Knowing About Python Exceptions Sooner

Here, print(‘orange’) does not run because it occurs after the return statement. Once the return statement runs, our function stops immediately and ignores everything that occurs afterward.

But can we bypass this restriction using the finally block in try-except-finally?

Regardless, the code inside the finally block will run. Even after the return statement in the function.

9 Things I Regret Not Knowing About Python Exceptions Sooner

Here, we print (‘apple’) in the try block and return 1. But even after executing the return statement, we still print (‘orange’) in the finally block.

This is useful if we have code that needs to run regardless, such as closing files or closing database connections (otherwise it may lead to memory leaks and other issues).

9) raise Exception1 from Exception2

In more complex applications, we may want to raise a chain of exceptions instead of a single exception. This way, we can better trace the exact source and cause of an exception.

To do this, we can use the syntax raise Exception1 from Exception2.

Here’s a simple example:

9 Things I Regret Not Knowing About Python Exceptions Sooner

Here, we first force a ZeroDivisionError.

In our except block, we raise another ValueError from the previously raised ZeroDivisionError. This way, we get a ValueError caused by the ZeroDivisionError.

To find and check the cause of the exception, we can simply use the .__cause__ attribute.

Edit /Fan Ruiqiang

Audit / Fan Ruiqiang

Review / Fan Ruiqiang

Click Below

Follow Us

Leave a Comment