This tutorial will teach you how to handle errors in Python effectively. You’ll learn about exceptions, try-except blocks, custom exceptions, and best practices for debugging and maintaining robust code.

Beginner30 minutes

Step 1: Understanding Python Exceptions

In Python, errors are represented as exceptions. When an error occurs, Python raises an exception, which can be caught and handled to prevent your program from crashing. Common built-in exceptions include ValueError, TypeError, IndexError, and KeyError.

Exceptions are objects that contain information about the error, such as its type and a message describing what went wrong.

print(10 / 0)  # Raises ZeroDivisionError

Practice Exercise

Write a program that attempts to divide two numbers. If the denominator is zero, catch the ZeroDivisionError and print a user-friendly message.

Show Solution
try:
    numerator = 10
    denominator = 0
    result = numerator / denominator
    print(result)
except ZeroDivisionError:
    print('Error: Cannot divide by zero.')

Step 2: Using Try-Except Blocks

The try-except block is used to catch and handle exceptions. Code inside the try block is executed, and if an exception occurs, the except block is executed instead of the program crashing.

You can also specify multiple except blocks to handle different types of exceptions.

try:
    num = int(input('Enter a number: '))
    print(10 / num)
except ValueError:
    print('Invalid input. Please enter a number.')
except ZeroDivisionError:
    print('Cannot divide by zero.')

Practice Exercise

Write a program that asks the user for a file name and attempts to open it. Handle FileNotFoundError and PermissionError separately, providing appropriate messages for each.

Show Solution
try:
    filename = input('Enter a file name: ')
    with open(filename, 'r') as file:
        print(file.read())
except FileNotFoundError:
    print('Error: File not found.')
except PermissionError:
    print('Error: Permission denied.')

Step 3: The Else and Finally Clauses

The else clause in a try-except block runs if no exceptions occur. The finally clause runs no matter what, making it ideal for cleanup actions like closing files or releasing resources.

- else: Executes when the try block succeeds.

- finally: Executes regardless of whether an exception occurred.

try:
    num = int(input('Enter a number: '))
    result = 10 / num
except ZeroDivisionError:
    print('Cannot divide by zero.')
else:
    print(f'Result: {result}')
finally:
    print('Execution complete.')

Practice Exercise

Write a program that reads a list of numbers from a file and calculates their sum. Use finally to ensure the file is closed, even if an error occurs.

Show Solution
try:
    with open('numbers.txt', 'r') as file:
        numbers = [int(line) for line in file]
        total = sum(numbers)
        print(f'Sum: {total}')
except FileNotFoundError:
    print('Error: File not found.')
except ValueError:
    print('Error: Invalid data in file.')
finally:
    print('File operation complete.')

Step 4: Raising Exceptions

You can raise exceptions manually using the raise keyword. This is useful for enforcing constraints or signaling errors in your code.

You can raise built-in exceptions or custom exceptions (covered in the next step).

def validate_age(age):
    if age < 0:
        raise ValueError('Age cannot be negative.')
    return age

try:
    validate_age(-5)
except ValueError as e:
    print(e)

Practice Exercise

Write a function that checks if a password meets the following criteria: at least 8 characters long and contains at least one number. Raise a ValueError if the password is invalid.

Show Solution
def validate_password(password):
    if len(password) < 8:
        raise ValueError('Password must be at least 8 characters long.')
    if not any(char.isdigit() for char in password):
        raise ValueError('Password must contain at least one number.')
    return True

try:
    validate_password('weak')
except ValueError as e:
    print(e)

Step 5: Creating Custom Exceptions

Custom exceptions allow you to define your own error types, making your code more readable and maintainable. To create a custom exception, define a class that inherits from Python's Exception class.

class InvalidEmailError(Exception):
    pass

def validate_email(email):
    if '@' not in email:
        raise InvalidEmailError('Invalid email address.')
    return email

try:
    validate_email('user.example.com')
except InvalidEmailError as e:
    print(e)

Practice Exercise

Create a custom exception called OutOfStockError. Write a function that checks if an item is in stock and raises this exception if it is not.

Show Solution
class OutOfStockError(Exception):
    pass

def check_stock(item, stock):
    if item not in stock:
        raise OutOfStockError(f'{item} is out of stock.')
    return True

try:
    stock = ['apple', 'banana', 'orange']
    check_stock('mango', stock)
except OutOfStockError as e:
    print(e)

Step 6: Logging Exceptions

Logging exceptions is crucial for debugging and monitoring applications. Python's logging module allows you to log errors with additional context, such as timestamps and severity levels.

Use logging.exception() within an except block to log the full traceback.

import logging

logging.basicConfig(filename='app.log', level=logging.ERROR)

try:
    10 / 0
except ZeroDivisionError:
    logging.exception('An error occurred:')

Practice Exercise

Write a program that reads a JSON file and logs any JSONDecodeError or FileNotFoundError to a file called errors.log.

Show Solution
import logging
import json

logging.basicConfig(filename='errors.log', level=logging.ERROR)

try:
    with open('data.json', 'r') as file:
        data = json.load(file)
except FileNotFoundError:
    logging.exception('File not found:')
except json.JSONDecodeError:
    logging.exception('Invalid JSON format:')

Step 7: Best Practices for Error Handling

  • Be specific with exceptions: Catch only the exceptions you expect and handle them appropriately.
  • Avoid bare except blocks: Catching all exceptions with a bare except: can hide bugs.
  • Use logging: Log exceptions for debugging and monitoring.
  • Clean up resources: Use finally or context managers to ensure resources are released.
  • Document exceptions: Clearly document the exceptions your functions can raise.
def divide(a, b):
    try:
        return a / b
    except ZeroDivisionError:
        logging.error('Attempted to divide by zero.')
        raise

# Example usage
try:
    divide(10, 0)
except ZeroDivisionError:
    print('Handled division by zero.')

Practice Exercise

Refactor the following code to follow best practices: catch only specific exceptions, log errors, and ensure resources are cleaned up.

try:
    file = open('data.txt', 'r')
    data = file.read()
    print(data)
except:
    print('An error occurred.')
finally:
    file.close()

Show Solution
import logging

logging.basicConfig(filename='app.log', level=logging.ERROR)

try:
    with open('data.txt', 'r') as file:
        data = file.read()
        print(data)
except FileNotFoundError:
    logging.exception('File not found:')
except PermissionError:
    logging.exception('Permission denied:')
except Exception as e:
    logging.exception(f'Unexpected error: {e}')

Sign in to take Cornell notes on this lesson — they save automatically and stay with your account.

Sign in

Click to access the login or register cheese