In software development, different types of errors can occur. They could be syntax errors, logical errors, or runtime errors.
Syntax errors most probably occur during the initial development phase and are a result of incorrect syntax. Syntax errors can be caught easily when the program is compiled for execution.
Logical errors, on the other hand, are a result of improper logical implementation. An example would be a program accessing an unsorted list assuming it to be sorted. Logical errors are the most difficult ones to track.
Runtime errors are the most interesting errors which occur, if we don't consider all the corner cases. An example would be trying to access a non-existent file.
- Handling Exceptions Using Try and Except
- Multiple Exceptions
- finally Clause
- User-Defined Exceptions
- Logging in Python
- Getting the Stack Trace
In this tutorial, we'll learn how to handle errors in Python and how to log the errors for a better understanding of what went wrong in the application.
Let's start with a simple program to add two numbers in Python. Our program takes in two parameters as input and prints the sum. Here is a Python program to add two numbers:
def addNumbers(a, b): print a + b addNumbers(5, 10)
Try running the above Python program, and you should have the sum printed.
While writing the above program, we didn't really consider the fact that anything can go wrong. What if one of the parameters passed is not a number?
We haven't handled that case, hence our program would crash with the following error message:
Traceback (most recent call last): File "addNumber.py", line 4, in <module> addNumbers('', 10) File "addNumber.py", line 2, in addNumbers print a + b TypeError: cannot concatenate 'str' and 'int' objects
We can handle the above issue by checking if the parameters passed are integers. But that won't solve the issue. What if the code breaks down due to some other reason and causes the program to crash? Working with a program which crashes on being encountered with an error is not a good sight. Even if an unknown error is encountered, the code should be robust enough to handle the crash gracefully and let the user know that something is wrong.
In Python, we use the
except statements to handle exceptions. Whenever the code breaks down, an exception is thrown without crashing the program. Let's modify the add number program to include the
def addNumbers(a, b): try: return a + b except Exception as e: return 'Error occurred : ' + str(e) print(addNumbers('', 10))
Python would process all code inside the
except statement. When it encounters an error, the control is passed to the
except block, skipping the code in between.
As seen in the above code, we have moved our code inside a
except statement. Try running the program and it should throw an error message instead of crashing the program. The reason for the exception is also returned as an exception message.
The above method handles unexpected exceptions. Let's have a look at how to handle an expected exception. Assume that we are trying to read a particular file using our Python program, but the file doesn't exist. In this case, we'll handle the exception and let the user know that the file doesn't exist when it happens. Have a look at the file reading code:
try: try: with open('file.txt') as f: content = f.readlines() except IOError as e: print(str(e)) except Exception as e: print(str(e))
In the above code, we have handled the file reading inside an
IOError exception handler. If the code breaks down because the
file.txt is unavailable, the error would be handled inside the
IOError handler. Similar to the
IOError exceptions, there are a lot more standard exceptions like
ImportError, to name a few.
We can handle multiple exceptions at a time by clubbing the standard exceptions as shown:
try: with open('file.txt') as f: content = f.readlines() print(content) except (IOError,NameError) as e: print(str(e))
The above code would raise both the
NameError exceptions when the program is executed.
Assume that we are using certain resources in our Python program. During the execution of the program, it encountered an error and only got executed halfway. In this case, the resource would be unnecessarily held up. We can clean up such resources using the
finally clause. Take a look at the below code:
try: filePointer = open('file.txt','r') try: content = filePointer.readline() finally: filePointer.close() except IOError as e: print(str(e))
If, during the execution of the above code, an exception is raised while reading the file, the
filePointer would be closed in the
So far, we have dealt with exceptions provided by Python, but what if you want to define your own custom exceptions? To create user-defined exceptions, you will need to create a class that inherits from the built-in
Exception class. An advantage of creating user-defined exceptions is that they will make sense in our programs. For example, suppose you had a program that ensures that the discounted price of an item is not more than the sale price. Let's create a custom exception for this type of error.
class PriceError(Exception): pass
Next, add the exception as follows:
def discount(price,discounted_price): if discounted_price > price: raise PriceError else: print("Discount applied")
In the code above, the
raise statement forces the
PriceError exception to occur.
Now, if you call the function with values where the
disounted_price is greater than the price, you will get an error, as shown below.
Traceback (most recent call last): File "/home/vat/Desktop/errors.py", line 75, in <module> discount(100,110) File "/home/vat/Desktop/errors.py", line 70, in discount raise PriceError __main__.PriceError
The error above does not provide a descriptive message; let's customize it to give a detailed message of what the error means.
class PriceError(Exception): def __init__(self, price,discounted_price): self.price = price self.disounted_price = discounted_price def __str__(self): return 'Discounted price greater than price'
Now, let's apply the error and call our function.
def discount(price,discounted_price): if discounted_price > price: raise PriceError(price,discounted_price) else: print("Discount applied") discount(100,110)
Now, if you call the function, you will get the following error:
(base) vaati@vaati-Yoga-9-14ITL5:~/Desktop/EVANTO2022$ python3 errors.py Traceback (most recent call last): File "/home/vaati/Desktop/EVANTO2022/errors.py", line 84, in <module> discount(100,110) File "/home/vaati/Desktop/EVANTO2022/errors.py", line 79, in discount raise PriceError(price,discounted_price) __main__.PriceError: Discounted price greater than price
When something goes wrong in an application, it becomes easier to debug if we know the source of the error. When an exception is raised, we can log the required information to track down the issue. Python provides a simple and powerful logging library. Let's have a look at how to use logging in Python.
try: logging.info('Trying to open the file') filePointer = open('file.txt','r') try: logging.info('Trying to read the file content') content = filePointer.readline() print(content) finally: filePointer.close() except IOError as e: logging.error('Error occurred ' + str(e))
As seen in the above code, first we need to import the logging Python library and then initialize the logger with the log file name and logging level. There are five logging levels:
CRITICAL. Here we have set the logging level to
INFO, so any message that has the level
INFO will be logged.
In the above code we had a single program file, so it was easier to figure out where the error had occurred. But what do we do when multiple program files are involved? In such a case, getting the stack trace of the error helps in finding the source of the error. The stack trace of the exception can be logged as shown:
import logging # initialize the log settings logging.basicConfig(filename = 'app.log', level = logging.INFO) try: filePointer = open('appFile','r') try: content = filePointer.readline() finally: filePointer.close() except IOError as e: logging.exception(str(e))
If you try to run the above program, on raising an exception the following error would be logged in the log file:
ERROR:root:[Errno 2] No such file or directory: 'appFile' Traceback (most recent call last): File "readFile.py", line 7, in <module> filePointer = open('appFile','r') IOError: [Errno 2] No such file or directory: 'appFile'
In this tutorial, we saw how to get started with handling errors in Python and using the logging module to log errors. We saw the usage of
finally statements, which are quite useful when dealing with error handling in Python. For more detailed information, I would recommend reading the official documentation on logging. Also have a look at the documentation for handling exceptions in Python.
This post has been updated with contributions from Esther Vaati. Esther is a software developer and writer for Envato Tuts+.