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.
try-except
block
Exception handling – 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 Error
ORexcept Error as err
– This method handles one error at a time and we can use multipleexcept
statements to handle multiple errors.as err
is used when we want to log the error or want to do some operations based on thaterr
object.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 justexcept
statement 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.")
else
block
Exception handling – 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.
finally
block
Exception handling – 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.
try-catch
block
Nested 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-catch
inelse
orfinally
blocktry-catch-else
inelse
orfinally
blocktry-catch-finally
inelse
orfinally
block
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
except
statements. - Keep the Try Block Small – Only wrap code that might throw an exception in the
try
block, and place normal logic outside it. - Use
finally
for Cleanup – Always use thefinally
block 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.