In this article, you will learn error and exception handling in Python.
By the end of the article, you’ll know:
- How to handle exceptions using the try, except, and finally statements
- How to create a custom exception
- How to raise an exceptions
- How to use built-in exception effectively to build robust Python programs
Table of contents
- What are Exceptions?
- What are Errors?
- Built-in Exceptions
- The try and except Block to Handling Exceptions
- Using try with finally
- Using try with else clause
- Raising an Exceptions
- Exception Chaining
- Custom and User-defined Exceptions
- Exception Lifecycle
What are Exceptions?
An exception is an event that occurs during the execution of programs that disrupt the normal flow of execution (e.g., KeyError Raised when a key is not found in a dictionary.) An exception is a Python object that represents an error..
In Python, an exception is an object derives from the BaseException class that contains information about an error event that occurred within a method. Exception object contains:
- Error type (exception name)
- The state of the program when the error occurred
- An error message describes the error event.
Exception are useful to indicate different types of possible failure condition.
For example, bellow are the few standard exceptions
In Python, we can throw an exception in the try block and catch it in except block.
Why use Exception
- Standardized error handling: Using built-in exceptions or creating a custom exception with a more precise name and description, you can adequately define the error event, which helps you debug the error event.
- Cleaner code: Exceptions separate the error-handling code from regular code, which helps us to maintain large code easily.
- Robust application: With the help of exceptions, we can develop a solid application, which can handle error event efficiently
- Exceptions propagation: By default, the exception propagates the call stack if you don’t catch it. For example, if any error event occurred in a nested function, you do not have to explicitly catch-and-forward it; automatically, it gets forwarded to the calling function where you can handle it.
- Different error types: Either you can use built-in exception or create your custom exception and group them by their generalized parent class, or Differentiate errors by their actual class
What are Errors?
On the other hand, An error is an action that is incorrect or inaccurate. For example, syntax error. Due to which the program fails to execute.
The errors can be broadly classified into two types:
- Syntax errors
- Logical errors
The syntax error occurs when we are not following the proper structure or syntax of the language. A syntax error is also known as a parsing error.
When Python parses the program and finds an incorrect statement it is known as a syntax error. When the parser found a syntax error it exits with an error message without running anything.
Common Python Syntax errors:
- Incorrect indentation
- Missing colon, comma, or brackets
- Putting keywords in the wrong place.
print("Welcome to PYnative") print("Learn Python with us..")
print("Learn Python with us..") ^ IndentationError: unexpected indent
Logical errors (Exception)
Even if a statement or expression is syntactically correct, the error that occurs at the runtime is known as a Logical error or Exception. In other words, Errors detected during execution are called exceptions.
Common Python Logical errors:
- Indenting a block to the wrong level
- using the wrong variable name
- making a mistake in a boolean expression
a = 10 b = 20 print("Addition:", a + c)
print("Addition:", a + c) NameError: name 'c' is not defined
The below table shows different built-in exceptions.
Python automatically generates many exceptions and errors. Runtime exceptions, generally a result of programming errors, such as:
- Reading a file that is not present
- Trying to read data outside the available index of a list
- Dividing an integer value by zero
|Raised when an |
|Raised when attribute assignment or reference fails.|
|Raised when the |
|Raised when a floating-point operation fails.|
|Raise when a generator’s close() method is called.|
|Raised when the imported module is not found.|
|Raised when the index of a sequence is out of range.|
|Raised when a key is not found in a dictionary.|
|Raised when the user hits the interrupt key (Ctrl+C or Delete)|
|Raised when an operation runs out of memory.|
|Raised when a variable is not found in the local or global scope.|
|Raised when system operation causes system related error.|
|Raised when a weak reference proxy is used to access a garbage collected referent.|
FilenotfoundError is raised when a file in not present on the disk
fp = open("test.txt", "r") if fp: print("file is opened successfully")
FileNotFoundError: [Errno 2] No such file or directory: 'test.txt'
except Block to Handling Exceptions
When an exception occurs, Python stops the program execution and generates an exception message. It is highly recommended to handle exceptions. The doubtful code that may raise an exception is called risky code.
To handle exceptions we need to use try and except block. Define risky code that can raise an exception inside the
try block and corresponding handling code inside the
try : # statements in try block except : # executed when exception occured in try block
The try block is for risky code that can raise an exception and the except block to handle error raised in a try block. For example, if we divide any number by zero, try block will throw
ZeroDivisionError, so we should handle that exception in the except block.
When we do not use
try…except block in the program, the program terminates abnormally, or it will be nongraceful termination of the program.
Now let’s see the example when we do not use
try…except block for handling exceptions.
a = 10 b = 0 c = a / b print("a/b = %d" % c)
Traceback (most recent call last): File "E:/demos/exception.py", line 3, in <module> c = a / b ZeroDivisionError: division by zero
We can see in the above code when we are divided by 0; Python throws an exception as
ZeroDivisionError and the program terminated abnormally.
We can handle the above exception using the
try…except block. See the following code.
try: a = 10 b = 0 c = a/b print("The answer of a divide by b:", c) except: print("Can't divide with zero. Provide different number")
Can't divide with zero. Provide different number
Catching Specific Exceptions
We can also catch a specific exception. In the above example, we did not mention any specific exception in the except block. Catch all the exceptions and handle every exception is not good programming practice.
It is good practice to specify an exact exception that the except clause should catch. For example, to catch an exception that occurs when the user enters a non-numerical value instead of a number, we can catch only the built-in ValueError exception that will handle such an event properly.
We can specify which exception
except block should catch or handle. A
try block can be followed by multiple numbers of
except blocks to handle the different exceptions. But only one exception will be executed when an exception occurs.
In this example, we will ask the user for the denominator value. If the user enters a number, the program will evaluate and produce the result.
If the user enters a non-numeric value then, the try block will throw a
ValueError exception, and we can catch that using a first catch block ‘except ValueError’ by printing the message ‘Entered value is wrong’.
And suppose the user enters the denominator as zero. In that case, the try block will throw a
ZeroDivisionError, and we can catch that using a second catch block by printing the message ‘Can’t divide by zero’.
try: a = int(input("Enter value of a:")) b = int(input("Enter value of b:")) c = a/b print("The answer of a divide by b:", c) except ValueError: print("Entered value is wrong") except ZeroDivisionError: print("Can't divide by zero")
Enter value of a:Ten Entered value is wrong
Enter value of a:10 Enter value of b:0 Can't divide by zero
Enter value of a:10 Enter value of b:2 The answer of a divide by b: 5.0
Handle multiple exceptions with a single except clause
We can also handle multiple exceptions with a single
except clause. For that, we can use an
tuple of values to specify multiple exceptions in an
Let’s see how to specifiy two exceptions in the single except clause.
try: a = int(input("Enter value of a:")) b = int(input("Enter value of b:")) c = a / b print("The answer of a divide by b:", c) except(ValueError, ZeroDivisionError): print("Please enter a valid value")
Python provides the
finally block, which is used with the try block statement. The
finally block is used to write a block of code that must execute, whether the
try block raises an error or not.
finally block is used to release the external resource. This block provides a guarantee of execution.
Clean-up actions using
Sometimes we want to execute some action at any cost, even if an error occurred in a program. In Python, we can perform such actions using a finally statement with a try and except statement.
The block of code written in the finally block will always execute even there is an exception in the try and except block.
If an exception is not handled by except clause, then finally block executes first, then the exception is thrown. This process is known as clean-up action.
try: # block of code # this may throw an exception finally: # block of code # this will always be executed # after the try and any except block
try: a = int(input("Enter value of a:")) b = int(input("Enter value of b:")) c = a / b print("The answer of a divide by b:", c) except ZeroDivisionError: print("Can't divide with zero") finally: print("Inside a finally block")
Enter value of a:20 Enter value of b:5 The answer of a divide by b: 4.0 Inside a finally block
Enter value of a:20 Enter value of b:0 Can't divide with zero Inside a finally block
In the above example, we can see we divide a number by 0 and get an error, and the program terminates normally. In this case, the
finally block was also executed.
Sometimes we might want to run a specific block of code. In that case, we can use
else block with the
try-except block. The
else block will be executed if and only if there are no exception is the
try block. For these cases, we can use the optional
else statement with the
Why to use
else block with try?
Use else statemen with try block to check if try block executed without any exception or if you want to run a specific code only if an exception is not raised
try: # block of code except Exception1: # block of code else: # this code executes when exceptions not occured
tryblock for risky code that can throw an exception.
exceptblock to handle error raised in a
elseblock is executed if there is no exception.
try: a = int(input("Enter value of a:")) b = int(input("Enter value of b:")) c = a / b print("a/b = %d" % c) except ZeroDivisionError: print("Can't divide by zero") else: print("We are in else block ")
Enter value of a: 20 Enter value of b:4 a/b = 5 We are in else block
Enter value of a: 20 Enter value of b:0 Can't divide by zero
Raising an Exceptions
In Python, the
raise statement allows us to throw an exception. The single arguments in the
raise statement show an exception to be raised. This can be either an exception object or an
Exception class that is derived from the
raise statement is useful in situations where we need to raise an exception to the caller program. We can raise exceptions in cases such as wrong data received or any validation failure.
Follow the below steps to raise an exception:
- Create an exception of the appropriate type. Use the existing built-in exceptions or create your won exception as per the requirement.
- Pass the appropriate data while raising an exception.
- Execute a raise statement, by providing the exception class.
The syntax to use the
raise statement is given below.
In this example, we will throw an exception if interest rate is greater than 100.
def simple_interest(amount, year, rate): try: if rate > 100: raise ValueError(rate) interest = (amount * year * rate) / 100 print('The Simple Interest is', interest) return interest except ValueError: print('interest rate is out of range', rate) print('Case 1') simple_interest(800, 6, 8) print('Case 2') simple_interest(800, 6, 800)
Case 1 The Simple Interest is 384.0 Case 2 interest rate is out of range 800
The exception chaining is available only in Python 3. The
raise statements allow us as optional
from statement, which enables chaining exceptions. So we can implement exception chaining in python3 by using
raise…from clause to chain exception.
When exception raise, exception chaining happens automatically. The exception can be raised inside
finally block section. We also disabled exception chaining by using
from None idiom.
try: a = int(input("Enter value of a:")) b = int(input("Enter value of b:")) c = a/b print("The answer of a divide by b:", c) except ZeroDivisionError as e: raise ValueError("Division failed") from e # Output: Enter value of a:10 # Enter value of b:0 # ValueError: Division failed
In the above example, we use exception chaining using
raise...from clause and raise
ValueError division failed.
Custom and User-defined Exceptions
Sometimes we have to define and
raise exceptions explicitly to indicate that something goes wrong. Such a type of exception is called a user-defined exception or customized exception.
The user can define custom exceptions by creating a new class. This new exception class has to derive either directly or indirectly from the built-in class
Exception. In Python, most of the built-in exceptions also derived from the
class Error(Exception): """Base class for other exceptions""" pass class ValueTooSmallError(Error): """Raised when the input value is small""" pass class ValueTooLargeError(Error): """Raised when the input value is large""" pass while(True): try: num = int(input("Enter any value in 10 to 50 range: ")) if num < 10: raise ValueTooSmallError elif num > 50: raise ValueTooLargeError break except ValueTooSmallError: print("Value is below range..try again") except ValueTooLargeError: print("value out of range...try again") print("Great! value in correct range.")
Enter any value in 10 to 50 range: 5 Value is below range..try again Enter any value in 10 to 50 range: 60 value out of range...try again Enter any value in 10 to 50 range: 11 Great! value in correct range.
In the above example, we create two custom classes or user-defined classes with names,
ValueTooLargeError.When the entered value is below the range, it will raise V
alueTooSmallError and if the value is out of then, it will
Customizing Exception Classes
We can customize the classes by accepting arguments as per our requirements. Any custom exception class must be Extending from
BaseException class or subclass of
In the above example, we create a custom class that is inherited from the base class
Exception. This class takes one argument age. When entered age is negative, it will raise
class NegativeAgeError(Exception): def __init__(self, age, ): message = "Age should not be negative" self.age = age self.message = message age = int(input("Enter age: ")) if age < 0: raise NegativeAgeError(age) # Output: # raise NegativeAgeError(age) # __main__.NegativeAgeError: -9
Enter age: -28 Traceback (most recent call last): File "E:/demos/exception.py", line 11, in raise NegativeAgeError(age) main.NegativeAgeError: -28
- When an exception is raised, The runtime system attempts to find a handler for the exception by backtracking the ordered list of methods calls. This is known as the call stack.
- If a handler is found (i.e., if
exceptblock is located), there are two cases in the
exceptblock; either exception is handled or possibly re-thrown.
- If the handler is not found (the runtime backtracks to the method chain’s last calling method), the exception stack trace is printed to the standard error console, and the application stops its execution.
def sum_of_list(numbers): return sum(numbers) def average(sum, n): # ZeroDivisionError if list is empty return sum / n def final_data(data): for item in data: print("Average:", average(sum_of_list(item), len(item))) list1 = [10, 20, 30, 40, 50] list2 = [100, 200, 300, 400, 500] # empty list list3 =  lists = [list1, list2, list3] final_data(lists)
Average: 30.0 Traceback (most recent call last): File "E:/demos/exceptions.py", line 17, in final_data(lists) File "E:/demos/exceptions.py", line 11, in final_data print("Average:", average(sum_of_list(item), len(item))) Average: 300.0 File "E:/demos/exceptions.py", line 6, in average return sum / n ZeroDivisionError: division by zero
The above stack trace shows the methods that are being called from main() until the method created an exception condition. It also shows line numbers.
Several built-in exceptions represent warning categories. This categorization is helpful to be able to filter out groups of warnings.
The warning doesn’t stop the execution of a program it indicates the possible improvement
Below is the list of warning exception
|Warning||Base class for warning categories|
|UserWarning||Base class for warnings generated by user code|
|DeprecationWarning||Warnings about deprecated features|
|PendingDeprecationWarning||Warnings about features that are obsolete and expected to be deprecated in the future, but are not deprecated at the moment.|
|SyntaxWarning||Warnings about dubious syntax|
|RuntimeWarning||Warnings about the dubious runtime behavior|
|FutureWarning||Warnings about probable mistakes in module imports|
|ImportWarning||Warnings about probable mistakes in module imports|
|UnicodeWarning||Warnings related to Unicode data|
|BytesWarning||Warnings related to bytes and bytearray.|
|ResourceWarning||Warnings related to resource usage|