Skip to Content
Course content

140: Catching Multiple Exception Types

Click on the "Edit" button in the top corner of the screen to edit your slide content.

In the previous lessons, we looked at how to wrap risky code in a try block and handle a single exception. That works great when there's only one thing that can go wrong, but real-world code is messier. Usually, a single block of logic can fail for several different reasons, and you have to decide if those reasons deserve the same response or different ones.

Building a simple payment processor

Let's build a small function that processes a payment amount. I want this function to take a value, convert it to a float, and then ensure it's positive. It sounds simple, but users are unpredictable. They might pass in a string that isn't a number, or they might pass in None by accident.

def process_payment(amount):
    try:
        # Convert to float in case it's a string
        numeric_amount = float(amount)
        if numeric_amount <= 0:
            raise ValueError("Amount must be positive.")
        print(f"Processing payment of ${numeric_amount:.2f}")
    except ValueError:
        print("Invalid amount entered. Please use a number.")

This handles the ValueError, but if someone passes None into the function, float(None) raises a TypeError. Right now, my program would just crash. I could add another except block, but here is where I usually start thinking about efficiency.

Grouping similar failures

In this specific case, whether it's a ValueError (like the string "abc") or a TypeError (like the value None), the end result is the same: the input is garbage. I don't need two different error messages for the user; I just need them to provide a valid number.

Instead of writing two separate blocks with the exact same print statement, I can catch multiple exceptions in one go by passing them as a tuple to the except clause.

def process_payment(amount):
    try:
        numeric_amount = float(amount)
        if numeric_amount <= 0:
            raise ValueError("Amount must be positive.")
        print(f"Processing payment of ${numeric_amount:.2f}")
    except (ValueError, TypeError):
        print("Invalid input. Please provide a valid positive number.")

Notice the parentheses around ValueError, TypeError. This is crucial. If you forget the parentheses, Python thinks you're trying to assign the exception instance to a variable named TypeError, which will lead to a very confusing bug.

Correcting a logic mistake

I just realized I made a mistake in my design. By grouping ValueError and TypeError together, I've accidentally lumped my "Amount must be positive" check in with the "This isn't a number" check. Both raise a ValueError, but they are very different problems. One is a data type issue; the other is a business logic issue.

If I want to tell the user why it failed—distinguishing between "that's not a number" and "you can't pay a negative amount"—I need to be more surgical. I'll keep the TypeError grouped with the conversion error, but I'll pull the business logic check into its own handler.

def process_payment(amount):
    try:
        # This part can throw TypeError or ValueError (if it's a bad string)
        numeric_amount = float(amount)
        
        if numeric_amount <= 0:
            # I'll use a custom message here
            raise ValueError("Negative amount")
            
        print(f"Processing payment of ${numeric_amount:.2f}")

    except (TypeError, ValueError) as e:
        # Check if it was my specific "Negative amount" error
        if str(e) == "Negative amount":
            print("Error: You cannot process a payment of zero or less.")
        else:
            print("Error: The input provided is not a valid number.")

Actually, an even cleaner way to do this is just to order my except blocks from most specific to most general. But the tuple approach is your best friend whenever you have a handful of different exceptions that truly require the exact same recovery steps.




📋 Practical Task

Exercise: The Robust Configuration Loader

You are writing a function load_config(config_dict, key) that retrieves a setting from a dictionary. The function should try to convert the value to an integer.

Write the function so that it handles the following scenarios using a tuple of exceptions in a single except block:

  • The key is missing from the dictionary (KeyError).
  • The value associated with the key cannot be converted to an integer (ValueError).
  • The value associated with the key is a type that int() cannot handle, such as a list (TypeError).

If any of these three errors occur, the function should print "Configuration error: Invalid or missing setting." and return a default value of 0. If the value is successfully converted, return the integer.

Rating
0 0

There are no comments for now.

to be the first to leave a comment.