Exceptions are errors that occur during program execution. These errors can be caused by various factors such as invalid user input, file not found, division by zero, etc.
In Python, exceptions are events that occur during the execution of a program that disrupt the normal flow of the program’s instructions. When an error occurs, Python generates an exception that can be handled using exception handling. Exception handling ensures the program continues executing or stops gracefully without unexpected crashes. Exception handling is important because of following reasons:
- Graceful Termination – Exception handling helps to avoid abrupt halts and unexpected crashes of the program. This allows the program to conclude gracefully (either continue executing or stops gracefully), log details, or even attempt recovery.
- Custom Error Responses – Exception handling allows us to define how specific types of errors should be dealt with. It means we can define actions to be taken when a particular exception occurs.
- Maintaining Program Flow – Exception handling allows us to decide how to handle errors instead of letting Python stop your program.
Exception handling – try-except block
In Python, exception handling is done using the try-except block. If any error occurs inside the try block, Python looks for an except block to handle the error.
try:
# Code that might raise an exception
except SomeException:
# Code to handle the exception
try:
result = 12 / 0 # attempt to divide by zero will raise ZeroDivisionError
except ZeroDivisionError: # except block can handle the error
print("Cannot divide by zero!")
In this example, attempting to divide by zero raises a ZeroDivisionError, which is handled by the except block.
Multiple Exceptions Handling
We can catch multiple exceptions by specifying them in a tuple or handling them separately using multiple except blocks. Syntax of multiple exceptions handling in two ways:
except ErrorORexcept Error as err– This method handles one error at a time and we can use multipleexceptstatements to handle multiple errors.as erris used when we want to log the error or want to do some operations based on thaterrobject.except (TypeError, NameError)ORexcept (TypeError, NameError) as err– This method is used when we have to perform same action for multiple exceptions. Then we can use single except statement to handle multiple exceptions.except– Using justexceptstatement without defining any error, it will catch all exceptions.
try:
num = int(input("Enter a number: ")) # Take user input from the console
result = 12 / num
except ValueError: # Raise ValueError is user input is not number
print("Invalid input! Please enter a valid number.") # Statement to execute if raised ValueError
except ZeroDivisionError: # Raise ZeroDivisionError is user input is zero
print("You cannot divide by zero!") # Statement to execute if raised ZeroDivisionError
In this example, a ValueError occurs if the user input isn’t an integer, and a ZeroDivisionError occurs if the user tries to divide by zero.
try:
del number # Delete the variable number
except AttributeError as err: # handle AttributeError
print("error", err) # Statement to execute if raised AttributeError
except (TypeError, NameError) as err: # handle TypeError and NameError
print("error", err) # Statement to execute if raised either TypeError or NameError
In this example, except (TypeError, NameError) as err handles multiple exceptions with single except statement.
Catch All Exceptions
To handle all exceptions, we can use a generic except block. However, this practice is generally discouraged because it may mask other errors unintentionally.
try:
result = 10 / 0 # raise ZeroDivisionError
except: # will catch all exceptions
print("An error occurred.")
Exception handling – else block
In Python exception handling, else block is used with try-except block. else block is optional. The else block is executed only if no exceptions occur in the try block. It can be useful for defining what should happen when everything works fine. Following is the syntax for try-except-else block.
try:
# code that might raise an exception
except SomeException:
# code to handle the exception
else:
# code that runs if no exceptions were raised
try:
result = 12 / 2 # number division
except ZeroDivisionError: # catch ZeroDivisionError
print("Cannot divide by zero!") # statement to execute, for ZeroDivisionError
else: # else block, execute when no error occurs
print(f"Result is: {result}") # else block statement to execute
In this example, else block statement will be executed when no ZeroDivisionError is raised.
try:
number = 10 # declare and initialize a number
del number # delete the number
except AttributeError as err: # catch AttributeError
print("error", err) # statement to execute, for AttributeError
except NameError as err: # catch NameError
print("error", err) # statement to execute, for NameError
else: # else block, execute when no error occurs
print("else executed - only when no error is raised")
In this example, else block statement will be executed when either of AttributeError OR NameError is not raised.
Exception handling – finally block
In Python exception handling, finally block is used with try-except block. finally block is optional. The finally block is used to define code that should be executed no matter what, whether an exception occurred or not. It is often used for cleanup tasks, such as closing files or releasing resources. Following is the syntax for try-except-finally block.
try:
# code that might raise an exception
except SomeException:
# code to handle the exception
finally:
# code that will run no matter what
try:
f = open("description.txt", "r") # open file in read and text mode
except FileNotFoundError: # catch FileNotFoundError
print("File not found!") # statement to execute, for FileNotFoundError
finally: # finally block, execute either error raised or not
print("This runs whether an exception occurs or not.") # finally block statement
In this example, finally block statement will be executed irrespective of FileNotFoundError is raised or not.
Nested try-catch block
In Python, we can have nested try-catch block along with else or finally block. Nested structure can be in any combinations of try-catch-else-finally blocks, like:
try-catchinelseorfinallyblocktry-catch-elseinelseorfinallyblocktry-catch-finallyinelseorfinallyblock
Example of nested try-catch block:
try:
course_file = open("course_details.txt")
except FileNotFoundError as err:
print("error", err)
finally:
print("finally statement is always executed, irrespective of error raised or not")
try: # try-catch block in finally block
course_file.close()
except NameError as err:
print("error", err)
raise and chain of exceptions
In Python, we can raise exceptions explicitly in our code using the raise keyword. This is useful for enforcing constraints or ensuring that the program follows a certain flow. Syntax to raise exceptions:
raise SomeException("Error message")
course_list = [] # declare an empty list
try: # try block
if len(course_list) <= 0: # check if length of list is less than or equal to 0
raise ValueError("error, no course is defined") # raise ValueError with message
except ValueError as err: # catch ValueError
print("error", err) # print error
In this program, ValueError is raised if length of the list is less than or equal to zero. ValueError is catched using the except block.
Chain of exceptions
In Python, chain of exceptions is by default. Chain of exceptions happen when function_A raised an error_A and function_B catches error_A to raise error_B.
def file_operations():
try:
read_file() # call to read_file() function
except FileNotFoundError as err: # catch FileNotFoundError raised from read_file() function
print("read_file error - ", err) # print error
raise IOError("file operations failed with error") # raise IOError
def read_file():
raise FileNotFoundError("file with given name not found") # raise FileNotFoundError
file_operations() # call to file_operations() function
In this program, read_file() function raise FileNotFoundError which is caught in file_operations() function and then file_operations() function raise IOError.
Custom Exceptions
In Python, we can define our own exceptions by creating a class that inherits from Python’s built-in Exception class. This allows us to provide more specific and meaningful error messages for our application. Syntax to create a custom exception:
class CustomError(Exception): # CustomErro class inherits Exception class
def __init__(self, message): # init function for CustomError class
self.message = message
super().__init__(self.message)
try:
raise CustomError("This is a custom error.") # raise CustomError with message
except CustomError as e: # catch CustomError
print(e)
Base Error Class
We can create a base Error class for Exception and then we use that class to create other exception classes. Syntax with base Error class and multiple error classes based on that base class.
# exception class in sequence from child to parent
print("exception class in sequence from child to parent")
class BaseError(Exception): # BaseError class inherits Exception class
print("in Parent2 class")
class ParentError(BaseError):
print("in Parent1 class") # ParentError class inherits BaseError class
class ChildError(ParentError):
print("in Child class") # ChildError class inherits ParentError class
for class_type in [ChildError, ParentError, BaseError]: # for loop to raise all three exception
try:
raise class_type()
except ChildError: # catch ChildError
print("error in Child class")
except ParentError: # catch ParentError
print("error in Parent class")
except BaseError: # catch BaseError
print("error in Base class")
Custom Error Class Example
class Error(Exception): # base Error class for Exception
"""Base class for exceptions in this module."""
pass
class CourseNotFoundError(Error): # CourseNotFoundError inherits Error class
"""Exception raised when course is not found. # Docstring for CourseNotFoundError class
Attributes:
name -- input name for which the exception raised
message -- explanation of the error
"""
def __init__(self, name, message): # init method for CourseNotFoundError class
self.name = name
self.message = message
class CourseNameError(Error): # CourseNameError inherits Error class
"""Raised when an training is not in progress. # Docstring for CourseNotFoundError class
Attributes:
id -- id for the training
status -- status of training
message -- explanation of why the training is not in progress
"""
def __init__(self, id, name, message): # init method for CourseNameError class
self.id = id
self.name = name
self.message = message
try:
raise(CourseNotFoundError("PHP", "Course is not started")) # raise CourseNotFoundError
except CourseNotFoundError as error:
print("exception occur for - ", error.name, " with message - ", error.message)
try:
raise(CourseNameError("C5", "PHP4", "Name fo course provided is wrong")) # raise CourseNameError
except CourseNameError as error:
print("exception occur for - ", error.id, error.name, " with message - ", error.message)
Exception Handling Best Practices
- Handle Specific Exceptions – Catch only the exceptions we expect. Avoid generic
exceptstatements. - Keep the Try Block Small – Only wrap code that might throw an exception in the
tryblock, and place normal logic outside it. - Use
finallyfor Cleanup – Always use thefinallyblock for resource management, such as closing files or network connections. - Don’t Suppress Errors – Use exception handling to manage errors, not to hide them. Catch specific errors and log them for future debugging.
Summary
In this article, we learned about Exception Handling in Python. Following topics were discussed:
- Exception handling – try-except block
- Exception handling – else block
- Exception handling – finally block
- Nested try-catch block
- raise and chain of exceptions
- Custom Exceptions
- Exception Handling Best Practices
Code – Github Repository
All code snippets and programs for this article and for Python tutorial, can be accessed from Github repository – Comments and Docstring in Python.