Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 6

Graceful Error Handling with try/except

Recommended resources

Welcome to the next lesson in our Python foundations module. In our previous session, we dove into the world of JSON, learning how to parse data from APIs into Python dictionaries and serialize our dictionaries back into JSON strings. We concluded by touching on how a try/except block could save our program from crashing if it received malformed JSON.

Today, we will expand on that specific use case to cover the general principle of error handling. The learning outcome for this lesson is to handle potential runtime errors gracefully using try/except blocks. This is a cornerstone of writing robust, professional software. Any application that interacts with the outside world—be it through user input, files, or network APIs—must be prepared for unexpected situations. Mastering error handling is a significant step toward your goal of building production-ready API applications.

The "Why": Easier to Ask for Forgiveness than Permission

In many programming languages, a common pattern is "Look Before You Leap" (LBYL). This means you check if a condition is safe before performing an action. For example: "check if a file exists, and if it does, open it."

Python, however, often favors a different philosophy: "Easier to Ask for Forgiveness than Permission" (EAFP). This means you just go ahead and try the action, and if it fails, you handle the resulting error. This approach can lead to cleaner and more efficient code, especially when dealing with external resources where the state can change between a check and an action. The try/except block is the mechanism that enables this philosophy.

Python Try-Except Tutorial: Best Practices and Real-World ...

This article from DataCamp provides a great introduction to the philosophy behind Python's approach to error handling.

Read the section Why Focus on try-except?. It explains why EAFP is the common style in Python and how it helps create robust applications.


The Anatomy of Error Handling

The full structure for handling exceptions includes four keywords: try, except, else, and finally. Let's break down what each part does.

This diagram shows the execution flow for `try`, `except`, `else`, and `finally`. The `try` block contains the code that might fail. The `except` block runs if an error occurs. The `else` block runs only if there were no errors. The `finally` block runs no matter what.
  1. try: The code inside this block is what Python will attempt to execute.
  2. except: If an error (an "exception") occurs in the try block, Python immediately stops executing the try block and looks for a matching except block to run. This is where you put your error-handling logic.
  3. else: The code in this block runs only if the try block completes successfully without raising any exceptions. It's useful for separating your "happy path" logic from the initial, risky operation.
  4. finally: The code here runs always, regardless of whether an exception occurred or not. This is essential for cleanup tasks, like closing a file or a network connection, to ensure resources are released properly.

Corey Schafer's tutorial on this topic provides an excellent visual and practical walkthrough of this entire structure.

Python Tutorial: Using Try/Except Blocks for Error Handling

This video by Corey Schafer clearly demonstrates the purpose of each part of the try/except structure using a file-handling example.

First, watch from the beginning until the introduction to see a basic example of catching a FileNotFoundError. Next, watch the explanation of the else block. Pay attention to his reasoning for why you'd move success-oriented code into the else block instead of keeping it in the try block. Finally, watch the segment on the finally block to understand how it guarantees execution for cleanup.


Best Practice: Be Specific with Your Exceptions

A common mistake for beginners is to use a broad except clause that catches every possible error.




# Avoid this!
try:



    # some risky code
    result = 10 / 0
except:
    print("Something went wrong!")

This is dangerous because it silences all errors, including ones you didn't anticipate, like typos in your code (NameError) or even system-level issues. It makes debugging incredibly difficult. The best practice is to catch only the specific exceptions you expect.

You can handle different exceptions in different ways, and you can even access the exception object itself to get more information.

Python Tutorial: Using Try/Except Blocks for Error Handling

Let's return to the Corey Schafer video to see how to handle exceptions with the right level of specificity.

Watch the section from catching specific exceptions. This clearly demonstrates how a general except can hide a different, unexpected bug. Then, watch the segment on handling multiple exceptions. Notice how you can stack except blocks and use as e to print the actual error message.

The key rules for effective error handling are:

  • Be specific: Catch ValueError or FileNotFoundError, not just a generic Exception.
  • Keep try blocks tight: Only wrap the one or two lines of code that could actually cause the error you're trying to catch. Don't wrap large blocks of unrelated code.
  • Don't silence errors: Avoid except: pass unless you have a very good, documented reason. It's better to log the error or inform the user.

Real-World Application: Handling API and Network Errors

Now, let's apply this directly to your goal of API engineering. When your application makes a network request to an API, a multitude of things can go wrong:

  • The server could be down.
  • Your internet connection could fail (ConnectionError).
  • The request could take too long and time out (Timeout).
  • You could have made a mistake in the request, or you might not have permission, resulting in an error response from the server (e.g., 404 Not Found, 401 Unauthorized).
These are the standard categories for HTTP response codes. Your error handling will primarily focus on catching issues related to 4xx (client errors) and 5xx (server errors), as well as network-level failures.

The requests library, which we'll use extensively, has a built-in way to handle this. The response.raise_for_status() method will check the HTTP status code of the response. If the code is a client or server error (4xx or 5xx), it will automatically raise an HTTPError exception.

This pattern is perfect for our EAFP philosophy: we make the request, ask requests to raise an exception if it failed, and then catch that specific exception.

How To Handle Errors & Exceptions with Requests and Python

This short video from John Watson Rooney is focused specifically on handling errors with the requests library.

Watch from the beginning. It will walk you through: The two main types of errors: ConnectionError and HTTPError. How raise_for_status() works to turn bad status codes into Python exceptions. How to wrap your request in a try...except block to gracefully handle the HTTPError. Using the more general requests.exceptions.RequestException to catch any error from the library.

Let's look at a comprehensive code example that puts this all together. This pattern is what production-level code looks like.

Python requests best practices for data engineers

The Y42 blog provides an excellent, robust example of handling various network-related exceptions.

Focus on the section Handle exceptions gracefully. Study the try/except block carefully. Notice how it handles HTTPError, ConnectionError, and Timeout specifically, and then has a final RequestException to catch anything else. This is the exact kind of defensive programming you'll need when building reliable API clients.

Inside the except requests.exceptions.HTTPError as e: block, you can even inspect e.response.status_code to take different actions based on whether the error was a 404, 500, etc. This allows for very fine-grained control over your application's behavior in response to specific API failures.


Conclusion

In this lesson, you've moved from seeing try/except as a niche tool for one problem (JSONDecodeError) to understanding it as a fundamental pillar of robust Python programming.

Here are the key takeaways:

  • Python's error handling often follows the EAFP philosophy: try an operation and handle the exception if it fails.
  • The full try/except/else/finally structure allows you to separate your main logic, error handling, success-case logic, and cleanup code.
  • It is a crucial best practice to catch specific exceptions rather than using a generic except: block, as this prevents you from accidentally hiding bugs.
  • In API engineering, you will constantly use try/except with response.raise_for_status() to handle network failures and error responses from APIs gracefully.

In our next lesson, we will tackle another crucial aspect of production-ready code: accessing sensitive data like API keys securely from environment variables. As you'll see, error handling will be important there too, for example, to manage cases where a required API key is missing.