Exception Handling with Python

10 min read

In this article, by Ninad Sathaye, author of the book, Learning Python Application Development, you will learn techniques to make the application more robust by handling exceptions

Specifically, we will cover the following topics:

  • What are the exceptions in Python?
  • Controlling the program flow with the try…except clause
  • Dealing with common problems by handling exceptions
  • Creating and using custom exception classes

(For more resources related to this topic, see here.)

Exceptions

Before jumping straight into the code and fixing these issues, let’s first understand what an exception is and what we mean by handling an exception.

What is an exception?

An exception is an object in Python. It gives us information about an error detected during the program execution. The errors noticed while debugging the application were unhandled exceptions as we didn’t see those coming. Later in the article,you will learn the techniques to handle these exceptions.

The ValueError and IndexErrorexceptions seen in the earlier tracebacks are examples of built-in exception types in Python. In the following section, you will learn about some other built-in exceptions supported in Python.

Most common exceptions

Let’s quickly review some of the most frequently encountered exceptions. The easiest way is to try running some buggy code and let it report the problem as an error traceback! Start your Python interpreter and write the following code:

Here are a few more exceptions:

As you can see, each line of the code throws a error tracebackwith an exception type (shown highlighted). These are a few of the built-in exceptions in Python. A comprehensive list of built-in exceptions can be found in the following documentation:https://docs.python.org/3/library/exceptions.html#bltin-exceptions

Python provides BaseException as the base class for all built-in exceptions. However, most of the built-in exceptions do not directly inherit BaseException. Instead, these are derived from a class called Exception that in turn inherits from BaseException. The built-in exceptions that deal with program exit (for example, SystemExit) are derived directly from BaseException. You can also create your own exception class as a subclass of Exception. You will learn about that later in this article.

Exception handling

So far, we saw how the exceptions occur. Now, it is time to learn how to use thetry…except clause to handle these exceptions. The following pseudocode shows a very simple example of the try…except clause:

Let’s review the preceding code snippet:

  • First, the program tries to execute the code inside thetryclause.
  • During this execution, if something goes wrong (if an exception occurs), it jumps out of this tryclause. The remaining code in the try block is not executed.
  • It then looks for an appropriate exception handler in theexceptclause and executes it.

The exceptclause used here is a universal one. It will catch all types of exceptions occurring within thetryclause. Instead of having this “catch-all” handler, a better practice is to catch the errors that you anticipate and write an exception handling code specific to those errors. For example, the code in thetryclause might throw an AssertionError. Instead of using the universalexcept clause, you can write a specific exception handler, as follows:

Here, we have an except clause that exclusively deals with AssertionError. What it also means is that any error other than the AssertionError will slip through as an unhandled exception. For that, we need to define multipleexceptclauses with different exception handlers. However, at any point of time, only one exception handler will be called. This can be better explained with an example. Let’s take a look at the following code snippet:

Thetry block calls solve_something(). This function accepts a number as a user input and makes an assertion that the number is greater than zero. If the assertion fails, it jumps directly to the handler, except AssertionError.

In the other scenario, with a > 0, the rest of the code in solve_something() is executed. You will notice that the variable xis not defined, which results in NameError. This exception is handled by the other exception clause, except NameError. Likewise, you can define specific exception handlers for anticipated errors.

Raising and re-raising an exception

Theraisekeyword in Python is used to force an exception to occur. Put another way, it raises an exception. The syntax is simple; just open the Python interpreter and type:

>>> raise AssertionError("some error message")

This produces the following error traceback:

Traceback (most recent call last): 
  File "<stdin>", line 1, in <module>
AssertionError :  some error message

In some situations, we need to re-raise an exception. To understand this concept better, here is a trivial scenario. Suppose, in thetryclause, you have an expression that divides a number by zero. In ordinary arithmetic, this expression has no meaning. It’s a bug! This causes the program to raise an exception called ZeroDivisionError. If there is no exception handling code, the program will just print the error message and terminate.

What if you wish to write this error to some log file and then terminate the program? Here, you can use anexceptclause to log the error first. Then, use theraisekeyword without any arguments to re-raise the exception. The exception will be propagated upwards in the stack. In this example, it terminates the program. The exception can be re-raised with the raise keyword without any arguments.

Here is an example that shows how to re-raise an exception:

As can be seen, adivision by zeroexception is raised while solving the a/b expression. This is because the value of variable b is set to 0. For illustration purposes, we assumed that there is no specific exception handler for this error. So, we will use the general except clause where the exception is re-raised after logging the error. If you want to try this yourself, just write the code illustrated earlier in a new Python file, and run it from a terminal window. The following screenshot shows the output of the preceding code:

The else block of try…except

There is an optionalelseblock that can be specified in the try…except clause. The elseblock is executed only ifno exception occurs in the try…except clause. The syntax is as follows:

Theelseblock is executed before thefinallyclause, which we will study next.

finally…clean it up!

There is something else to add to the try…except…else story:an optional finally clause. As the name suggests, the code within this clause is executed at the end of the associated try…except block. Whether or not an exception is raised, the finally clause, if specified, willcertainly get executed at the end of thetry…except clause. Imagine it as anall-weather guaranteegiven by Python! The following code snippet shows thefinallyblock in action:

Running this simple code will produce the following output:

$ python finally_example1.py
Enter a number: -1
Uh oh..Assertion Error. 
Do some special cleanup 

The last line in the output is theprintstatement from the finally clause.

The code snippets with and without the finally clause are are shown in the following screenshot. The code in the finallyclause is assured to be executed in the end, even when the except clause instructs the code to return from the function.

Thefinallyclause is typically used to perform clean-up tasks before leaving the function. An example use case is to close a database connection or a file. However, note that, for this purpose you can also use thewith statement in Python.

Writing a new exception class

It is trivial to create a new exception class derived from Exception. Open your Python interpreter and create the following class:

>>> class GameUnitError(Exception):
...     pass
... 
>>>

That’s all! We have a new exception class,GameUnitError, ready to be deployed. How to test this exception? Just raise it. Type the following line of code in your Python interpreter:

>>> raise GameUnitError("ERROR: some problem with game unit")

Raising the newly created exception will print the following traceback:

>>> raise GameUnitError("ERROR: some problem with game unit")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
__main__.GameUnitError: ERROR: some problem with game unit

Copy the GameUnitError class into its own module, gameuniterror.py, and save it in the same directory as attackoftheorcs_v1_1.py.

Next, update the attackoftheorcs_v1_1.py file to include the following changes:

First, add the following import statement at the beginning of the file:

from gameuniterror import GameUnitError

The second change is in the AbstractGameUnit.heal method. The updated code is shown in the following code snippet. Observe the highlighted code that raises the custom exception whenever the value ofself.health_meterexceeds that of self.max_hp.

With these two changes, run heal_exception_example.py created earlier. You will see the new exception being raised, as shown in the following screenshot:

Expanding the exception class

Can we do something more with the GameUnitError class? Certainly! Just like any other class, we can define attributes and use them. Let’s expand this class further. In the modified version, it will accept an additional argument and some predefined error code. The updated GameUnitError class is shown in the following screenshot:

Let’s take a look at the code in the preceding screenshot:

  • First, it calls the __init__method of the Exceptionsuperclass and then defines some additional instance variables.
  • A new dictionary object,self.error_dict, holds the error integer code and the error information as key-value pairs.
  • The self.error_message stores the information about the current error depending on the error code provided.
  • The try…except clause ensures that error_dict actually has the key specified by thecodeargument. It doesn’t in the except clause, we just retrieve the value with default error code of 000.

So far, we have made changes to the GameUnitError class and the AbstractGameUnit.heal method. We are not done yet. The last piece of the puzzle is to modify the main program in the heal_exception_example.py file. The code is shown in the following screenshot:

Let’s review the code:

  • As the heal_by value is too large, the heal method in the try clause raises the GameUnitError exception.
  • The new except clause handles the GameUnitError exception just like any other built-in exceptions.
  • Within theexceptclause, we have twoprintstatements. The first one prints health_meter>max_hp!(recall that when this exception was raised in the heal method, this string was given as the first argument to the GameUnitError instance). The second print statement retrieves and prints the error_message attribute of the GameUnitError instance.

We have got all the changes in place. We can run this example form a terminal window as:

$ python heal_exception_example.py

The output of the program is shown in the following screenshot:

In this simple example, we have just printed the error information to the console. You can further write verbose error logs to a file and keep track of all the error messages generated while the application is running.

Summary

This article served as an introduction to the basics of exception handling in Python. We saw how the exceptions occur, learned about some common built-in exception classes, and wrote simple code to handle these exceptions using thetry…except clause.

The article also demonstrated techniques, such as raising and re-raising exceptions, using thefinally clause, and so on. The later part of the article focused on implementing custom exception classes. We defined a new exception class and used it for raising custom exceptions for our application.

With exception handling, the code is in a better shape.

Resources for Article:


Further resources on this subject:


Packt

Share
Published by
Packt

Recent Posts

Harnessing Tech for Good to Drive Environmental Impact

At Packt, we are always on the lookout for innovative startups that are not only…

2 months ago

Top life hacks for prepping for your IT certification exam

I remember deciding to pursue my first IT certification, the CompTIA A+. I had signed…

3 years ago

Learn Transformers for Natural Language Processing with Denis Rothman

Key takeaways The transformer architecture has proved to be revolutionary in outperforming the classical RNN…

3 years ago

Learning Essential Linux Commands for Navigating the Shell Effectively

Once we learn how to deploy an Ubuntu server, how to manage users, and how…

3 years ago

Clean Coding in Python with Mariano Anaya

Key-takeaways:   Clean code isn’t just a nice thing to have or a luxury in software projects; it's a necessity. If we…

3 years ago

Exploring Forms in Angular – types, benefits and differences   

While developing a web application, or setting dynamic pages and meta tags we need to deal with…

3 years ago