Maintenance

Site is under maintenance — quizzes are still available.

Go to quizzes

Handle Errors with Try Except Finally

Learn to handle errors with try, except, and finally in Python. This lesson covers the core concept, step-by-step implementation, practical exercise, and common troubleshooting.

Focus: handle errors with try, except, and finally

Sponsored

Every Python program eventually hits an unexpected situation — a missing file, invalid user input, or a network timeout. Without a safety net, one bad data point can crash your whole script. In this lesson, you'll learn to handle errors with try, except, and finally — Python's structured way to catch and respond to runtime problems without ugly exit messages or lost work.

The problem this lesson solves

Imagine you write a simple calculator that divides two numbers provided by a user. If someone enters 10 and 0, Python raises a ZeroDivisionError and your program stops immediately:

x = 10
y = 0
result = x / y  # raises ZeroDivisionError
print(result)    # never reached

Output (stderr):

Traceback (most recent call last):
  File "calc.py", line 3, in <module>
    result = x / y
ZeroDivisionError: division by zero

This is ungraceful — it leaks internal details to the user and leaves the program in an unpredictable state. The same problem occurs with file operations (FileNotFoundError), network calls (ConnectionError), or parsing invalid data (ValueError). Without error handling, you can't build robust, user-friendly applications.

Core concept / mental model

Think of error handling as airbags for your code. Normal execution is the smooth road. When a collision happens (an exception), the airbags deploy — that's the except block. After the crash, the finally block is like the cleanup crew that always shows up, regardless of whether an accident occurred.

In Python, an exception is an object that represents an error or unexpected event. The try block holds the code that might fail. You attach one or more except blocks that specify how to handle specific exception types. Optionally, a finally block runs after everything else — perfect for releasing resources like file handles or network connections.

Pro tip: Python's exception hierarchy is large. Always catch the most specific exception first, then fall back to a general Exception as a safety net.

How it works step by step

Basic structure of try/except/finally

try:
    # Code that might raise an exception
    risky_operation()
except SomeException:
    # What to do if that specific exception occurs
    handle_error()
finally:
    # Always runs, even if an exception happens
    cleanup()

Step 1 — The try block

Python executes every line inside try sequentially. If everything succeeds, it skips all except blocks and jumps to finally (if present).

Step 2 — An exception occurs

If an exception is raised inside try (either by Python or via raise), normal execution stops immediately. Python looks for a matching except block.

Step 3 — Exception matching

Python checks each except block in order. If the exception object is an instance of the listed exception class (or a subclass), that block runs. If no match is found, the exception propagates up the call stack and may crash the program.

Step 4 — The finally block

Regardless of what happened in try or except, the finally block always executes — even if you use return, break, or continue inside the try block.

Hands-on walkthrough

Example 1: Basic file read with error handling

def read_config(filename):
    try:
        with open(filename, 'r') as f:
            return f.read()
    except FileNotFoundError:
        print(f"Error: {filename} not found. Using defaults.")
        return "default settings"
    finally:
        print("File read attempt complete.")

content = read_config("missing.yaml")
print("Content:", content)

Output:

Error: missing.yaml not found. Using defaults.
File read attempt complete.
Content: default settings

Example 2: Catching multiple exception types

def safe_divide(a, b):
    try:
        result = a / b
    except ZeroDivisionError:
        print("Division by zero is not allowed.")
        result = None
    except TypeError:
        print("Both arguments must be numbers.")
        result = None
    else:
        # Runs only if no exception occurred
        print("Division succeeded.")
    finally:
        print("Operation finished.")
    return result

print(safe_divide(10, 2))
print("---")
print(safe_divide(10, 0))
print("---")
print(safe_divide(10, "a"))

Output:

Division succeeded.
Operation finished.
5.0
---
Division by zero is not allowed.
Operation finished.
None
---
Both arguments must be numbers.
Operation finished.
None

Pro tip: Use else after your except blocks to run code that should execute only when the try block succeeds — it's cleaner than putting that code inside try itself.

Example 3: Ensuring resource cleanup with finally

import sqlite3

def fetch_users():
    conn = sqlite3.connect("users.db")
    cursor = conn.cursor()
    try:
        cursor.execute("SELECT * FROM users")
        return cursor.fetchall()
    except sqlite3.DatabaseError as e:
        print(f"Database error: {e}")
        return []
    finally:
        cursor.close()
        conn.close()
        print("Database connection closed.")

print(fetch_users())

Output (if file doesn't exist):

Database error: unable to open database file
Database connection closed.
[]

The finally block guarantees the connection is closed, even if the database doesn't exist.

Example 4: Raising and re-raising exceptions

def validate_age(age):
    try:
        age = int(age)
        if age < 0:
            raise ValueError("Age cannot be negative")
        print(f"Valid age: {age}")
    except ValueError as e:
        print(f"Input error: {e}")
        # re-raise after logging
        raise
    finally:
        print("Validation complete.")

try:
    validate_age("-5")
except ValueError:
    print("Caught re-raised exception.")

Output:

Input error: Age cannot be negative
Validation complete.
Caught re-raised exception.

Compare options / when to choose what

Use Case Best Practice Alternative/Why
Catching known errors (e.g., division by zero) Specific except block Bare except: swallows KeyboardInterrupt — avoid it
Logging all exceptions without crashing try / except Exception / finally sys.excepthook for global handler
Temporary debugging try / except with raise inside Use warnings.warn() for non-critical issues
Cleaning up resources (files, sockets) Always use finally or context manager (with) with statement is cleaner when applicable

Pro tip: Favor context managers (with open(...) as f) over manual try/finally for resource cleanup — they handle finally internally.

Troubleshooting & edge cases

Mistake 1: Catching too broad an exception

try:
    risky()
except:  # bare except catches KeyboardInterrupt, SystemExit
    pass

Fix: Catch Exception (or a specific type).

Mistake 2: Ignoring exception details

try:
    x = int(inp)
except ValueError:
    pass  # silently swallows error

Fix: Log or display a meaningful message.

Mistake 3: Using finally to mask exceptions

def f():
    try:
        raise ValueError("original")
    finally:
        raise TypeError("mask")

Result: The TypeError replaces the original — you lose the root cause.

Mistake 4: Forgetting that finally runs even on return

def demo():
    try:
        return 1
    finally:
        print("I run before the return!")

Output: I run before the return! then function returns 1.

What you learned & what's next

You can now handle errors with try, except, and finally — protecting your programs from crashes, providing user-friendly messages, and guaranteeing cleanup. You understand the difference between specific and generic exception handling, and how to use else and finally effectively.

Next lesson: We'll explore custom exceptions — how to define your own error types to make your APIs more expressive and your debugging faster.

Practice recap

Mini challenge: Write a function safe_input(prompt, type_cast) that repeatedly asks for input until the user provides a valid value of the expected type. Use try/except inside a while loop to catch ValueError from type casting. Return the parsed value once successful.

Common mistakes

  • Catching bare except: — this catches KeyboardInterrupt and SystemExit, making programs impossible to stop gracefully. Always specify Exception or a concrete type.
  • Swallowing exceptions silently (empty except block) — you lose debugging information. At minimum, log the error or re-raise.
  • Putting too much code inside try — only wrap the smallest region that may fail. This prevents unintended exception handling from unrelated bugs.
  • Assuming except block code never fails — if your cleanup code raises an exception, it can mask the original error.

Variations

  1. Use contextlib.suppress to intentionally ignore specific exceptions without an empty except block: with suppress(FileNotFoundError): os.remove('temp').
  2. Use sys.excepthook to set a global unhandled exception handler for all exceptions that reach the top level.
  3. Use traceback module to capture and log full stack traces without crashing.

Real-world use cases

  • Reading configuration files: catch FileNotFoundError and fall back to default values instead of crashing the startup process.
  • Network API calls: use try/except to catch ConnectionError and TimeoutError, then retry or show a friendly error message.
  • Data validation in web forms: wrap int() or float() conversions in try/except ValueError to return user-friendly validation errors.

Key takeaways

  • Use try to guard code that may raise exceptions; only wrap the minimal risky section.
  • Catch specific exception types (e.g., FileNotFoundError) before the general Exception to avoid masking unexpected bugs.
  • Always include a finally block for cleanup actions (closing files, releasing locks) — it runs even if return or break is used.
  • Use else after except blocks to execute code only when no exception occurred — keeps try clean.
  • Never use a bare except: — you'll catch KeyboardInterrupt and SystemExit, making your program unresponsive.

Sponsored

Sponsored

Discussion

Questions, corrections, and tips help everyone reading this page.

0 comments

Add a comment

Shown publicly with your comment.

Be constructive · max 4,000 characters

No comments yet — start the thread.

Related tutorials, quizzes, and articles for this topic.