Exception Handling in Python

In my years of working with Python, I’ve found that exception handling is one of those skills that separates beginners from advanced developers. It’s like having a safety net when your code doesn’t behave as expected.

When I first started programming in Python, I would often see my applications crash unexpectedly. That’s when I learned the importance of properly handling exceptions.

In this comprehensive tutorial, I’ll walk you through everything you need to know about exception handling in Python – from the basics to advanced techniques that I use in my day-to-day work.

What is Exception Handling in Python?

Exception handling is Python’s way of responding to unexpected situations in your program. Instead of crashing when an error occurs, your program can catch these exceptions and respond appropriately.

Think of exceptions like unexpected detours on a road trip. Without proper handling, these detours would end your journey prematurely.

Check out this page to see all the tutorials related to object-oriented programming in Python.

Why Exception Handling is Essential

Before diving into the how, let’s understand why exception handling is crucial:

  1. Prevents application crashes – Your program continues running even when errors occur
  2. Improves user experience – Users see helpful error messages instead of cryptic traceback errors
  3. Makes debugging easier – You can isolate and identify problems more efficiently
  4. Allows for graceful degradation – Your application can fall back to alternative methods when something fails

Basic Exception Handling in Python

The most fundamental form of exception handling in Python uses the try and except blocks.

try:
    # Code that might raise an exception
    user_input = int(input("Enter a number: "))
    result = 100 / user_input
    print(f"100 divided by your number is: {result}")
except:
    # Code that executes if an exception occurs
    print("Something went wrong! Please try again.")

In this example, two potential exceptions could occur:

  • ValueError if the user enters text instead of a number
  • ZeroDivisionError if the user enters zero

Catching Specific Exceptions

While the basic approach works, it’s considered better practice to catch specific exceptions rather than using a blanket except clause.

try:
    user_input = int(input("Enter a number: "))
    result = 100 / user_input
    print(f"100 divided by your number is: {result}")
except ValueError:
    print("That's not a valid number!")
except ZeroDivisionError:
    print("You can't divide by zero!")

This approach lets you provide more targeted error messages and handle different errors differently.

Using the else Clause

The else clause in exception handling executes when no exceptions occur in the try block:

try:
    file = open("customer_data.txt", "r")
    content = file.read()
except FileNotFoundError:
    print("Sorry, the customer data file was not found.")
else:
    # This only runs if no exceptions occurred
    print(f"Successfully read {len(content)} characters from the file.")
    file.close()

I find this useful for keeping the “happy path” code separate from the error handling code.

The finally Clause

The finally clause executes regardless of whether an exception occurred or not:

try:
    file = open("sales_report.txt", "r")
    content = file.read()
except FileNotFoundError:
    print("Could not find the sales report.")
finally:
    # This code always runs
    print("Attempted to read the sales report.")
    # Make sure we close the file if it was opened
    if 'file' in locals() and not file.closed:
        file.close()

I frequently use finally for cleanup operations like closing files or database connections.

Learn more about File Handling in Python in this tutorial.

Raising Exceptions

Sometimes, you need to raise exceptions yourself. This is useful when you detect an error condition that Python wouldn’t automatically catch:

def process_age(age):
    if age < 0:
        raise ValueError("Age cannot be negative")
    if age > 120:
        raise ValueError("Are you sure this age is correct?")
    return f"Processing age: {age}"

try:
    print(process_age(-5))
except ValueError as error:
    print(f"Invalid age: {error}")

Creating Custom Exceptions

For special cases, you might want to create your own exception classes:

class InsufficientFundsError(Exception):
    """Raised when a transaction would result in a negative balance"""
    pass

def withdraw(balance, amount):
    if amount > balance:
        raise InsufficientFundsError(f"You tried to withdraw ${amount} but your balance is only ${balance}")
    return balance - amount

try:
    new_balance = withdraw(100, 150)
except InsufficientFundsError as e:
    print(f"Transaction failed: {e}")

Custom exceptions make your code more readable and maintainable, especially in larger applications.

Exception Chaining

Python allows you to chain exceptions to show the original cause of an error:

try:
    try:
        with open("config.json", "r") as file:
            import json
            config = json.load(file)
    except json.JSONDecodeError as json_err:
        raise ValueError("Configuration file has invalid format") from json_err
except ValueError as err:
    print(f"Error: {err}")
    print(f"Caused by: {err.__cause__}")

I’ve found this invaluable for diagnosing complex issues in production environments.

Catching Multiple Exceptions

You can catch multiple exception types in a single except clause:

try:
    # Reading and processing a data file
    with open("data.csv", "r") as file:
        first_line = file.readline()
        value = int(first_line.strip())
        result = 100 / value
except (FileNotFoundError, ValueError, ZeroDivisionError) as e:
    print(f"Data processing error: {type(e).__name__}: {e}")

This approach keeps your code cleaner when you want to handle different errors in the same way.

Context Managers and the with Statement

The with statement provides an elegant way to handle resources that need cleanup:

try:
    with open("transactions.log", "a") as log_file:
        log_file.write("Transaction started\n")
        # Some code that might raise an exception
        x = 1 / 0
        log_file.write("Transaction completed\n")
except ZeroDivisionError:
    print("Transaction failed due to division by zero")

The file is automatically closed when leaving the with block, even if an exception occurs.

Check out all the tutorials on the topic of Python Arrays.

Best Practices for Exception Handling

After working with Python for many years, here are my top recommendations:

  1. Be specific – Catch specific exceptions rather than using bare except clauses
  2. Keep it simple – Don’t overuse exception handling; use it only when necessary
  3. Don’t suppress exceptions – Empty except blocks are almost always a bad idea
  4. Document exceptions – Clearly document which exceptions your functions might raise
  5. Use context managers – The with statement is your friend for resource management
  6. Don’t use exceptions for flow control – Exceptions should be for exceptional circumstances

Real-World Example: API Data Fetching

Here’s a practical example showing exception handling while fetching data from an API:

import requests
import json
import time

def get_stock_price(ticker):
    """Get the current stock price for a given ticker symbol"""
    try:
        url = f"https://api.example.com/stocks/{ticker}"
        response = requests.get(url, timeout=5)
        
        # Check if the request was successful
        response.raise_for_status()
        
        # Parse the JSON response
        data = response.json()
        return data['price']
        
    except requests.exceptions.HTTPError as http_err:
        if response.status_code == 404:
            print(f"Stock ticker {ticker} not found")
        else:
            print(f"HTTP error occurred: {http_err}")
    except requests.exceptions.ConnectionError:
        print("Unable to connect to the stock API. Check your internet connection.")
    except requests.exceptions.Timeout:
        print("Request timed out. The stock service might be experiencing high load.")
    except requests.exceptions.RequestException as err:
        print(f"An error occurred while fetching stock data: {err}")
    except (KeyError, json.JSONDecodeError):
        print("Received invalid data format from the API")
    
    # If any error occurred, return None
    return None

# Example usage
price = get_stock_price("AAPL")
if price:
    print(f"Current Apple stock price: ${price}")

This example demonstrates how to handle various types of exceptions that might occur when working with external APIs.

Exception handling-related tutorials:

Conclusion

Exception handling is a fundamental skill for any Python developer. By properly implementing exception handling in your code, you can create more robust, user-friendly applications that gracefully handle errors instead of crashing.

Remember that the goal isn’t to suppress errors but to handle them appropriately – providing useful feedback, performing necessary cleanup, and ensuring your application can continue functioning whenever possible.

I hope this tutorial has given you a solid understanding of Python’s exception handling capabilities. Start incorporating these techniques into your code, and you’ll see immediate improvements in reliability and maintainability.

51 Python Programs

51 PYTHON PROGRAMS PDF FREE

Download a FREE PDF (112 Pages) Containing 51 Useful Python Programs.

Let’s be friends

Be the first to know about sales and special discounts.