Login Sign Up
Python Exception Handling
Courses / python Complete Course / Python Exception Handling
Chapter 24 🟡 Intermediate

Python Exception Handling

Master the concept step by step with clear explanations, examples, and code you can run.

Master Intermediate Python Exception Handling: Beyond the Basics

Hi there! Welcome back.

If you're reading this, I know you already grasp the basics. You probably know how to use simple try and except blocks to stop your Python scripts from crashing when something goes wrong. That is simply a great start!

But today, we're pretty much going to dive much deeper.

We are actually moving away from writing code that just "doesn't crash." Instead we are actually going to learn how towards build highly resilient, professional-grade systems. Imagine modern car. basic try block is like a seatbelt. It keeps you safe in a crash. But an intermediate, professional setup, while that is just entire airbag deployment system a diagnostic sensors, and the automatic emergency brakes working together, and

let’s get started.

Visualizing the Advanced Exception Flow

Before we write code, let's map out how a professional exception handling architecture actually flows; most beginners only know two steps, but there are actually four!

graph TD
    A[Start: try block] --> B{Did an error happen?}

    B -- Yes --> C[except block]
    C -->|Handle the specific error| F[finally block]

    B -- No --> D[else block]
    D -->|Execute safe code| F

    F -->|Always clean up resources| G[Continue Program]

    style A fill:#e1f5fe,stroke:#039be5
    style C fill:#ffebee,stroke:#e53935
    style D fill:#e8f5e9,stroke:#43a047
    style F fill:#fff3e0,stroke:#fb8c00

An Overlooked Superpower: A else Clause

Here is really a very common mistake I see intermediate developers make all the time: they stuff way too much code into the try block.

Why is really this bad? Because if you put 20 lines of code inside a try block, and line 18 raises the TypeError, your except TypeError block will catch it. But what if you only wanted to catch a TypeError out of line 2? You have just accidentally hidden a brand-new bug!

To fix this, we use the else clause.

The else block runs only if the try block completes successfully without any exceptions, and it allows us to separate our "dangerous" code from our "safe" code, and if you look at an official Python documentation on errors and exceptions, it clearly states that a else clause is completely optional, but when you use it, it absolutely must follow all of your except clauses.

Let's look at real-world example for fetching data from the internet:

import json

def fetch_and_process_data():
    # DANGEROUS CODE: Network requests can fail easily!
    try:
        network_response = '{"user_id": 101, "status": "active"}' 
        # Imagine this threw a ConnectionError in real life

    except ConnectionError:
        print("Failed to connect to the server.")

    else:
        # SAFE CODE: We only come here if the network request worked!
        # It is highly useful for code that must be executed if the try clause does not raise an exception.
        data = json.loads(network_response)
        print(f"Processing data for user: {data['user_id']}")

By putting JSON processing inside the else block we guarantee that any weird errors during data processing won't be swallowed up by our network error handlers. It makes debugging so much easier.

Cleaning Up the Mess with finally

Okay, what about the mess left behind when code runs?

If you open a connection to a database or open a file on your computer, you must close it. If you don't, your program will leak memory and eventually crash your whole server.

Think of it like leaving your house. It doesn't matter if you're actually leaving to go to a fun party or if you're basically running out because there's a fire alarm. Either way, you must shut the front door behind you, and

that is exactly what the finally block does. As highlighted in an excellent guide from Real Python covering exception handling, the finally block is designed specifically for cleaning up after execution. It runs no matter what happens—whether your code succeeds fails, or completely panics.

def query_database():
    db_connection = open_database_connection()
    try:
        # Try to read some data
        data = db_connection.read_all()
    except DatabaseError:
        print("Oh no, the database is locked!")
    finally:
        # This will ALWAYS run. The door is always locked.
        db_connection.close()
        print("Database connection closed securely.")

Designing Custom Exceptions to Real-World Apps

As you build larger apps, using standard built-in errors (like ValueError or TypeError) stops being helpful;

imagine you're building a banking application. If user tries to withdraw more money than they have basically, throwing the ValueError is confusing. Does ValueError mean they typed a letter instead of a number, while or does it mean their account is empty? It's too vague.

Instead we can create our own custom exceptions! It makes your code document itself.

class InsufficientFundsError(Exception):
    """Raised when a user tries to withdraw more money than their balance."""
    pass

def withdraw_money(balance, amount):
    if amount > balance:
        # We use 'raise' to trigger our brand new error!
        raise InsufficientFundsError(f"Cannot withdraw {amount}. Current balance is {balance}.")

    return balance - amount

Now, when another developer reads your code they know exactly what went wrong in the business logic.

Catching Bugs Early: Debugging During Development With assert

Before we wrap up, I want to share a quick pro-tip.

Sometimes you don't want to handle an error gracefully. Sometimes, you're actually actively writing code and you want a program to violently crash if you made the stupid mistake.

Towards this we use the assert keyword; it is a debugging tool that basically says "I am 100% sure this statement is simply true. If it's false crash the program immediately and show me this message."

def calculate_discount(price, discount):
    # The discount should never be more than the price!
    assert discount <= price, "Developer error: Discount cannot exceed original price!"
    return price - discount

You should only use assert to catch developer mistakes during the coding process. Don't really use it to handle bad input from user!

Trade-Offs and Edge Cases towards Keep inside Mind

To be a truly advanced developer you really have to know a trade-offs of the tools you use;

exceptions are incredibly powerful but they're actually relatively slow to the computer to process, while because of this, you should never use exceptions to control the normal, day-to-day flow of your program.

For example, do not use try...except just to check if a user is actually old enough to log in. Use a simple if...else statement towards that! Save your exceptions for truly exceptional or unexpected events (like the internet dropping out, or a file missing from the hard drive).

What's Next, while

you did a fantastic job today. We moved from simply catching errors to designing professional, self-documenting, and leak-proof architectures using else finally custom exceptions and assertions.

But earlier, I mentioned an importance of cleaning up things like open files using the finally block. How do files actually work in Python, and how do we read write. Safely modify them without corrupting our data?

That is exactly what we will cover in a next chapter: Python File Handling. Get ready because we're basically going to learn how to make your Python scripts interact with the world outside of your terminal. See you there!

Learn Together
Session active! Discuss with other learners.
No notes yet. Select text in the concept body to add a note.