Python: Error Handling Best Practices

python error handling

Errors are part of coding. Having a good understanding of error handling makes you a better developer. In this blog we will learn Python error handling best practices.

Why Error Handling is Important?
Error handling helps the application to gracefully tackle the error and prevents the application from crashing. It provides information about the error.

Overview of Python Error Handling
We can Handle errors in Python using exceptions. Exceptions are objects raised when errors occur during program execution. By handling the exceptions we can make our code failure proof.

What is an Exception in Python?

An exception is an object which is seen as an error that has occurred during code execution. When we get an exception the normal flow of the program is disturbed and control is transferred to an exception handler.

There are common Python exceptions

  1. SyntaxError : Occurs when we have syntax error in our code.
  2. ValueError: ValueError is raised when a function receives a valid parameter but the value of the parameter is invalid
  3. TypeError : Occurs when a function or operation is applied to an object of the wrong type. For example, if we try to add a number to a string, we get a type error.
  4. KeyError: Raised when key not found.

Python: Error Handling Best Practices

Catch only the exceptions you expect

When handling an error in the code we should only catch the exceptions that are anticipated. If you catch a generic exception like “Exception” it may mask the root cause of an error making it difficult for us to identify the root cause of an error. Below code only catches the possible errors you expect. We expect two specific exceptions i.e. ZeroDivisionError and TypeError.
Python Code
def divide_numbers(a, b):
    try:
        result = a / b
        return result
    except ZeroDivisionError:
        print("Error: Cannot divide by zero.")
    except TypeError:
        print("Error:  Both arguments must be numbers.")

Using the Logging Module for Error Logging

Use the logging module in Python to log the error details
Python code:
import logging

logging.basicConfig(level=logging.ERROR)
logging.error("An error occurred", exc_info=True)

Provide Meaningful Error Messages

Always provide meaningful error messages which help the developer in debugging the error. If we have a big code base and we use meaningful error messages it saves time and effort in understanding the issue.

Avoid Silencing Errors: Don't Ignore Them

Ignoring exceptions may cause bugs in the program and may lead to unintended flow of application. So it’s always important to handle the exceptions.

Using Warnings for Non-Critical Issues

Utilize the warnings module for issues that are not critical enough to raise exceptions.

Graceful Shutdowns and Cleanup on Errors

Implement cleanup actions within finally blocks or context managers to ensure resources are released properly.

Create Custom Exceptions for custom requirements

When developing an application we came across many different scenarios where we want to raise an error but we don’t have the correct exception. For cases like that we create custom exceptions.

Avoid Catching All Exceptions with a just except

Catching all exceptions can lead to unintended consequences just using ‘except’ Python.

def process_data(data):
    try:
        # Simulating some processing
        result = 10 / data  # This can raise a ZeroDivisionError
        print(f"Result: {result}")
    except:  # This will catch all exceptions, including SystemExit, KeyboardInterrupt, etc.
        print("An error occurred.")

# Test with various inputs
process_data(2)  # Should work fine
process_data(0)  # Should raise ZeroDivisionError, but caught by the bare except
process_data("a")  # Should raise TypeError, but also caught by the bare except

Output

Result: 5.0
An error occurred.
An error occurred.

Learn how to prevent sql inject in python.

Custom Exceptions in Python

Custom exceptions help us to create new exceptions which are application specific and provide more control over the application execution flow. For example we have added a custom exception for raising errors for insufficient balance.
class InsufficientFundsException(Exception):
    """Exception raised for errors in the account balance."""

    def __init__(self, balance, amount):
        self.balance = balance
        self.amount = amount
        self.message = f"Insufficient funds: Available balance is {balance}, but attempted to withdraw {amount}."
        super().__init__(self.message)

# Example function using the custom exception
def withdraw(balance, amount):
    if amount > balance:
        raise InsufficientFundsException(balance, amount)
    return balance - amount

# Usage
try:
    new_balance = withdraw(100, 150)
except InsufficientFundsException as e:
    print(e)
Output
Insufficient funds: Available balance is 100, but attempted to withdraw 150.

Python error handling patterns

Using try and except Block

The primary error handling mechanism in Python is the try and except block.

Python template code : The code shows a framework of try and except.

try:
    # Code that may raise an exception
except SomeException as e:
    # Handle the exception

Python code showing use of try and except to catch a ZeroDivisionError

try:
    # Code that may raise an exception
    result = 10 / 0  # This will raise a ZeroDivisionError
except ZeroDivisionError as e:
    # Handle the exception
    print(f"Error: {e}. You cannot divide by zero!")

output

Error: division by zero. You cannot divide by zero!

Using else and finally for Complete Control

WE can also use else and finally to enhance control over error handling:

Python template code :

try:
    # Code that may raise an exception
except SomeException:
    # Handle the exception
else:
    # Code to execute if no exception occurs
finally:
    # Code that will run regardless of exception occurrence

We will write a code for raising a zero division error with try except and finally Block.

def divide_numbers(num1, num2):
    try:
        result = num1 / num2
    except ZeroDivisionError:
        print("Error: Cannot divide by zero.")
    else:
        print(f"The result is: {result}")
    finally:
        print("Execution completed.")

# Example usage
divide_numbers(10, 2)  # This will succeed
divide_numbers(10, 0)  # This will raise an exception

Output

The result is: 5.0
Execution completed.
Error: Cannot divide by zero.
Execution completed.

Python API error handling best practices

Apply HTTP Status Codes whenever returning error or result

Return appropriate HTTP status codes to show the success or failure of requests so that the developer can direct the flow of the application.

  • 200 OK: Successful requests.
  • 400 Bad Request: Error on the Client-side (e.g., invalid input).
  • 401 Unauthorized: Authentication failures.
  • 403 Forbidden: Access denied.
  • 404 Not Found: Resource not found / Url not found / page not found. .
  • 500 Internal Server Error: Server-side errors.

Use Middleware for Global Error Handling

Use middleware to catch unhandled exceptions throughout the application.
Python code
from flask import Flask, jsonify

app = Flask(__name__)

@app.errorhandler(Exception)
def handle_exception(e):
    response = {
        "error": str(e),
        "status": 500
    }
    return jsonify(response), 500
The above code sets up a global error handler in Flask application. When a error occurs we get a JSON response with 500 as status code

Error Handling in Asynchronous Functions with async/await

A code example which a API developer can follow to cover major errors in async / await format is shown below. We will try to fetch JSON data from public API . If our request fails due  timeout error or connection error we will handle it properly.
Python code
import asyncio
import aiohttp

class FetchDataError(Exception):
    pass

async def fetch_data(url):
    async with aiohttp.ClientSession() as session:
        try:
            async with session.get(url, timeout=5) as response:
                response.raise_for_status()  # Raises an error for 4xx/5xx responses
                return await response.json()
        except asyncio.TimeoutError:
            raise FetchDataError("Request timed out")
        except aiohttp.ClientConnectionError:
            raise FetchDataError("Connection error occurred")
        except aiohttp.HttpProcessingError as e:
            raise FetchDataError(f"HTTP error: {e}")
        except Exception as e:
            raise FetchDataError(f"An unexpected error occurred: {e}")

async def my_async_function():
    url = "https://jsonplaceholder.typicode.com/posts/1"
    try:
        data = await fetch_data(url)
        print("Data fetched successfully:", data)
    except FetchDataError as e:
        print(f"Error in async operation: {e}")

# Running the asynchronous function
await my_async_function() #code is runned on google colab ipynb file 
Output:
Data fetched successfully: 
{'userId': 1, 'id': 1, 'title': 'sunt aut facere repellat provident occaecati excepturi optio reprehenderit', 
'body': 'quia et suscipit\nsuscipit recusandae consequuntur expedita et cum
reprehenderit molestiae ut ut quas totam
nostrum rerum est autem sunt rem eveniet architecto'}
You can also use the requests library in python to raise exception like connection error or timeout error.

Test Error Scenarios

A good developer always writes unit tests and integration test to ensure that API handles errors as required.

Conclusion

Summary of python error handling best practices

  • Catch specific exceptions and don’t generalize using ‘except’
  • Use the logging module in Python for debugging.
  • Provide logical error messages which would the user and developer know what is the issue
  • Avoid silencing errors because they alter the flow of the program..
  • Graceful Shutdowns and Cleanup on Errors
  • Using Warnings for Non-Critical Issues
  • Create custom exceptions when needed.
  • Properly handle errors in asynchronous code.
  • Always test your error handling code carefully.

References:
Errors and Exceptions

2 thoughts on “Python: Error Handling Best Practices”

  1. Pingback: Python got multiple values for argument

  2. Pingback: How to Import Python Files from the Same Directory -

Comments are closed.