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_()
.

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

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


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.
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:

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.

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.

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.

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:
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:

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.
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.

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

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.

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

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.
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.

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?
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.

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.

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:

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.

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:
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