- Exceptions are error scenarios that alter the normal execution flow of the program.
- The process of taking care of the possible exceptions is called exception handling.
- If exceptions are not handled properly, the program may terminate prematurely. It can cause data corruption or unwanted results.
- Python exception handling is achieved by three keyword blocks – try, except, and finally.
- The try block contains the code that may raise exceptions or errors.
- The except block is used to catch the exceptions and handle them.
- The catch block code is executed only when the corresponding exception is raised.
- There can be multiple catch blocks. We can also catch multiple exceptions in a single catch block.
- The finally block code is always executed, whether the program executed properly or it raised an exception.
- We can also create an “else” block with try-except block. The code inside the else block is executed if there are no exceptions raised.
How to Handle Exceptions in Python?
Let’s look at an example where we need exception handling.
def divide(x, y):
print(f'{x}/{y} is {x / y}')
divide(10, 2)
divide(10, 0)
divide(10, 4)
If we run the above program, we get the following output.
10/2 is 5.0
Traceback (most recent call last):
File "/Users/pankaj/Documents/PycharmProjects/PythonTutorialPro/hello-world/exception_handling.py", line 6, in <module>
divide(10, 0)
File "/Users/pankaj/Documents/PycharmProjects/PythonTutorialPro/hello-world/exception_handling.py", line 2, in divide
print(f'{x}/{y} is {x / y}')
ZeroDivisionError: division by zero
The second call to the divide() function raised ZeroDivisionError exception and the program terminated.
We never got the output of the third call to divide() method because we didn’t do exception handling in our code.
Let’s rewrite the divide() method with proper exception handling. If someone tries to divide by 0, we will catch the exception and print an error message. This way the program will not terminate prematurely and the output will make more sense.
def divide(x, y):
try:
print(f'{x}/{y} is {x / y}')
except ZeroDivisionError as e:
print(e)
divide(10, 2)
divide(10, 0)
divide(10, 4)
Output:
10/2 is 5.0
division by zero
10/4 is 2.5

What is BaseException Class?
The BaseException class is the base class of all the exceptions. It has four sub-classes.
- Exception – this is the base class for all non-exit exceptions.
- GeneratorExit – Request that a generator exit.
- KeyboardInterrupt – Program interrupted by the user.
- SystemExit – Request to exit from the interpreter.
Some Built-In Exception Classes
Some of the built-in exception classes in Python are:
- ArithmeticError – this is the base class for arithmetic errors.
- AssertionError – raised when an assertion fails.
- AttributeError – when the attribute is not found.
- BufferError
- EOFError – reading after end of file
- ImportError – when the imported module is not found.
- LookupError – base exception for lookup errors.
- MemoryError – when out of memory occurs
- NameError – when a name is not found globally.
- OSError – base class for I/O errors
- ReferenceError
- RuntimeError
- StopIteration, StopAsyncIteration
- SyntaxError – invalid syntax
- SystemError – internal error in the Python Interpreter.
- TypeError – invalid argument type
- ValueError – invalid argument value
Some Built-In Warning Classes
The Warning class is the base class for all the warnings. It has the following sub-classes.
- BytesWarning – bytes, and buffer related warnings, mostly related to string conversion and comparison.
- DeprecationWarning – warning about deprecated features
- FutureWarning – base class for warning about constructs that will change semantically in the future.
- ImportWarning – warning about mistakes in module imports
- PendingDeprecationWarning – warning about features that will be deprecated in future.
- ResourceWarning – resource usage warnings
- RuntimeWarning – warnings about dubious runtime behavior.
- SyntaxWarning – warning about dubious syntax
- UnicodeWarning – Unicode conversion-related warnings
- UserWarning – warnings generated by the user code
Handling Multiple Exceptions in a Single Except Block
A try block can have multiple except blocks. We can catch specific exceptions in each of the except blocks.
def divide(x, y):
try:
print(f'{x}/{y} is {x / y}')
except ZeroDivisionError as e:
print(e)
except TypeError as e:
print(e)
except ValueError as e:
print(e)
The code in every except block is the same. In this scenario, we can handle multiple exceptions in a single except block. We can pass a tuple of exception objects to an except block to catch multiple exceptions.
def divide(x, y):
try:
print(f'{x}/{y} is {x / y}')
except (ZeroDivisionError, TypeError, ValueError) as e:
print(e)
Catch-All Exceptions in a Single Except Block
If we don’t specify any exception class in the except block, it will catch all the exceptions raised by the try block. It’s beneficial to have this when we don’t know about the exceptions that the try block can raise.
The empty except clause must be the last one in the exception handling chain.
def divide(x, y):
try:
print(f'{x}/{y} is {x / y}')
except ZeroDivisionError as e:
print(e)
except:
print("unknown error occurred")
Using else Block with try-except
The else block code is optional. It’s executed when there are no exceptions raised by the try block.
def divide(x, y):
try:
print(f'{x}/{y} is {x / y}')
except ZeroDivisionError as e:
print(e)
else:
print("divide() function worked fine.")
divide(10, 2)
divide(10, 0)
divide(10, 4)
Output:

The else block code executed twice when the divide() function try block worked without any exception.
Using finally Block with try-except
The finally block code is executed in all the cases, whether there is an exception or not. The finally block is used to close resources and perform clean-up activities.
def divide(x, y):
try:
print(f'{x}/{y} is {x / y}')
except ZeroDivisionError as e:
print(e)
else:
print("divide() function worked fine.")
finally:
print("close all the resources here")
divide(10, 2)
divide(10, 0)
divide(10, 4)
Output:

Python Exception Handling Syntax
Now that we have seen everything related to exception handling in Python, the final syntax is:
try -> except 1...n -> else -> finally
We can have many except blocks for a try block. But, we can have only one else and finally block.
Creating Custom Exception Class
We can create a custom exception class by extending Exception class. The best practice is to create a base exception and then derive other exception classes. Here are some examples of creating user-defined exception classes.
class EmployeeModuleError(Exception):
"""Base Exception Class for our Employee module"""
pass
class EmployeeNotFoundError(EmployeeModuleError):
"""Error raised when employee is not found in the database"""
def __init__(self, emp_id, msg):
self.employee_id = emp_id
self.error_message = msg
class EmployeeUpdateError(EmployeeModuleError):
"""Error raised when employee update fails"""
def __init__(self, emp_id, sql_error_code, sql_error_msg):
self.employee_id = emp_id
self.error_message = sql_error_msg
self.error_code = sql_error_code
The naming convention is to suffix the name of exception class with “Error”.
Raising Exceptions
We can use raise keyword to throw an exception from our code. Some of the possible scenarios are:
- Function input parameters validation fails
- Catching an exception and then throwing a custom exception
class ValidationError(Exception):
pass
def divide(x, y):
try:
if type(x) is not int:
raise TypeError("Unsupported type")
if type(y) is not int:
raise TypeError("Unsupported type")
except TypeError as e:
print(e)
raise ValidationError("Invalid type of arguments")
if y is 0:
raise ValidationError("We can't divide by 0.")
try:
divide(10, 0)
except ValidationError as ve:
print(ve)
try:
divide(10, "5")
except ValidationError as ve:
print(ve)
Output:
We can't divide by 0.
Unsupported type
Invalid type of arguments
Nested try-except Blocks Example
We can have nested try-except blocks in Python. In this case, if an exception is raised in the nested try block, the nested except block is used to handle it. In case the nested except is not able to handle it, the outer except blocks are used to handle the exception.
x = 10
y = 0
try:
print("outer try block")
try:
print("nested try block")
print(x / y)
except TypeError as te:
print("nested except block")
print(te)
except ZeroDivisionError as ze:
print("outer except block")
print(ze)
Output:
outer try block
nested try block
outer except block
division by zero
Python Exception Handling Best Practices
- Always try to handle the exception in the code to avoid abnormal termination of the program.
- When creating a custom exception class, suffix its name with “Error”.
- If the except clauses have the same code, try to catch multiple exceptions in a single except block.
- Use finally block to close heavy resources and remove heavy objects.
- Use else block to log successful execution of the code, send notifications, etc.
- Avoid bare except clause as much as possible. If you don’t know about the exceptions, then only use it.
- Create module-specific exception classes for specific scenarios.
- You can catch exceptions in an except block and then raise another exception that is more meaningful.
- Always raise exceptions with meaningful messages.
- Avoid nested try-except blocks because it reduces the readability of the code.