Skip to main content

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

Learn Python try-except with real-world examples, best practices, and common pitfalls. Write cleaner, more reliable error-handling code.
Aug 26, 2025  · 10 min read

When Python code runs into problems at runtime, it often raises an exception. Left unhandled, exceptions will crash your program. However, with try-except blocks, you can catch them, recover gracefully, and keep your application running.

This tutorial isn’t about the basics of exceptions, we already cover that in our Exception & Error Handling in Python guide. Instead, here we’ll take a deep dive into try-except in practice: how to structure your blocks, avoid common mistakes, and apply best practices that make your code more reliable in real-world scenarios.

By the end, you’ll understand not only how try-except works, but also how to use it the Pythonic way; writing error-handling code that’s clear, maintainable, and production-ready.

If you’re still on your Python learning journey, I recommend checking out the Python Programming Fundamentals skill track, which will help you build all the essential skills. 

Why Focus on try-except?

Error handling in Python is fundamentally about designing programs that can deal with the unexpected. And while there are many tools for handling errors, try-except is the backbone of Python’s approach.

Why does it matter so much?

  • It reflects Python’s philosophy. In Python, the common style is EAFP; Easier to Ask Forgiveness than Permission. Instead of checking everything up front (does this file exist? is this input valid? is the server available?), you just try the operation. If it fails, you catch the exception and handle it. This leads to shorter, cleaner code.
  • It keeps code running in real-world environments. User input will be messy, files will go missing, APIs will fail, and networks will drop. With try-except, you can write code that doesn’t collapse at the first problem but instead reacts intelligently.
  • It’s flexible enough for beginners and professionals. A beginner might use try-except to catch a ValueError when converting input to an integer. A production system might use it to log errors, retry failed operations, or raise custom exceptions that make debugging easier.

That’s why this article zooms in on try-except. Mastering it is the difference between writing scripts that only work in perfect conditions and writing software that’s robust, maintainable, and production-ready.

Anatomy of a Try‑Except Block

At its simplest:

try:
    risky_thing()
except SomeError:
    handle_it()

When Python encounters an error inside the try block, it jumps to the matching except block instead of crashing the program.

You can also extend this pattern with else and finally clauses:

try:
    do_something()
except ValueError:
    print("Bad value!")
else:
    print("All good.")
finally:
    clean_up()

Here’s a basic working example:

try:
    x = int(input("Enter a number: "))
except ValueError:
    print("That wasn’t a number.")
else:
    print("You entered", x)
finally:
    print("Done.")

The except handles the error, else runs only if things worked, and finally always runs, even if you hit Ctrl‑C or exit.

You can master the fundamentals of user input handling in Python, from basic prompts to advanced validation and error handling techniques using our Python User Input: Handling, Validation, and Best Practices tutorial. 

Handling Multiple Exceptions

Sometimes, one except just doesn’t cut it. Different code paths can fail in different ways, and catching everything under the sun with a bare except: isn’t exactly helpful; it can hide bugs and make debugging a headache.

Say you're trying to convert something to an integer and then divide it. Here's what not to do:

try:
    result = int(user_input) / 2
except:
    print("Something went wrong.")

This catches everything, including things you probably didn’t intend to hide, like typos in variable names or unexpected exceptions that point to real problems.

Instead, be specific:

try:
    result = int(user_input) / 2
except ValueError:
    print("That wasn't a number.")
except ZeroDivisionError:
    print("Division by zero?")

If you’ve got several exception types that need to be treated the same way, you can group them like this:

try:
    result = some_function()
except (TypeError, ValueError):
    print("Something was wrong with the data.")

This way, you’re still clear about what might go wrong, without duplicating the same block over and over.

And then there’s except Exception, which is better than a bare except, but still a bit too wide. You’re better off targeting the specific errors you expect.

Using else and finally Blocks

When the code inside the try block succeeds and no exceptions are raised, Python executes the else block. 

try:
    user_id = get_user_id()
except LookupError:
    print("User not found.")
else:
    print("Welcome, user", user_id)

This helps you separate your exception-handling logic from the happy path, making it easier to read.

Then there’s finally. It doesn’t care what happened; it runs no matter what. Exception? Still runs. No exception? Still runs. Program crashes with Ctrl‑C? Still runs. Great for cleanup stuff:

try:
    f = open("data.txt")
    process(f)
except IOError:
    print("Couldn’t open file.")
finally:
    f.close()

Just remember, if the file never opened, f might not even exist, so be careful. You could use Python’s context manager (with open(...) as f:) instead when working with files. It’s safer.

If you want to learn key techniques, such as exception handling and error prevention, to handle the KeyError  exception in Python effectively, I recommend our Python KeyError Exceptions and How to Fix Them tutorial. 

Python try-except Best Practices and Common Pitfalls

Let’s be honest: writing try‑except blocks is a bit of an art. Done well, they keep your program from crashing. Done poorly, and they bury bugs so deep you won’t notice them until your app crashes and users are complaining.

Here are some habits worth building:

Keep try blocks tight. Don’t wrap a hundred lines of code inside a single try. That just makes it harder to know what caused the problem. Instead, wrap only the code that might fail:

# Good
try:
    value = int(data)
except ValueError:
    print("Couldn’t convert.")

# Bad
try:
    # Tons of unrelated logic
    value = int(data)
    do_more()
    something_else()
except ValueError:
    print("Huh?")

Avoid checking conditions before doing the action if it’s easier just to try and catch the error. Python has a name for this: EAFP, Easier to Ask Forgiveness than Permission. If you're checking whether a file exists and then opening it, you're creating a race condition. Instead:

try:
    with open("file.txt") as f:
        content = f.read()
except FileNotFoundError:
    print("No file.")

This pattern avoids a common problem where the file might disappear between the check and the open call. Just try the thing. If it fails, catch the failure.

Also, avoid silencing errors by catching everything and doing nothing. Don’t do this:

try:
    something()
except:
    pass

Unless you really know what you’re doing, this is where bugs go to hide and multiply. If you must silence, be specific:

try:
    something()
except TimeoutError:
    # okay to ignore in this case
    pass

And log the errors somewhere. Swallowing them completely means future you will have no clue what went wrong.

Built-in vs Custom Exceptions

Python comes with plenty of exceptions baked in, and they cover a surprising number of cases. You’ve probably bumped into some already:

  • ValueError: when something has the right type but an invalid value, like int("hello").
  • TypeError: when you try to use an operation on the wrong type, like adding a string and a number.
  • ZeroDivisionError: you guessed it, dividing by zero.
  • FileNotFoundError: trying to open a file that’s not there.
  • KeyError: when a key is missing in a dictionary.

These are useful, and in most scripts or apps, they’re more than enough. But sometimes you’ll want to raise something more descriptive, something that makes sense in your project’s world, not just Python’s.

Let’s say you’re writing an app that processes online orders, and you want to raise an error when a user tries to buy something that’s out of stock. You could just use ValueError, but that’s a bit generic. It doesn’t tell the next person reading your code what happened.

Here’s where custom exceptions shine.

class OutOfStockError(Exception):
    pass

def check_inventory(product_id):
    if not in_stock(product_id):
        raise OutOfStockError(f"Product {product_id} is out of stock.")

By creating the custom exception, you're adding a layer of meaning. You're also giving yourself more control; you can catch just this one specific situation:

try:
    check_inventory("shirt-001")
except OutOfStockError:
    print("Sorry, that item is sold out.")

It’s a small thing, but it makes your code easier to understand and maintain, especially in bigger projects. And it plays well with logging and monitoring, custom exception names are much easier to search for than a vague ValueError.

Logging, Re-Raising, and Exception Chaining

There’s this moment that happens when debugging: you see a traceback, stare at it, and realize you have no clue why something failed. That’s where logging comes in. It’s not just about recording errors, it’s about giving future you (or your team) the breadcrumbs to figure out what went wrong.

Let’s say you’re catching an exception and want to log it before moving on:

import logging
logging.basicConfig(level=logging.ERROR)

try:
    do_something()
except ValueError as e:
    logging.error("Failed to do something: %s", e)
    raise

That raise at the end is important, it rethrows the same exception after logging it. Without it, you’ve just swallowed the error. Sometimes that’s fine, but usually, it’s not.

Then there's the raise from trick. This one’s for when you’re handling one error but need to raise another, and you don’t want to lose the original one. Python lets you chain them:

try:
    connect_to_database()
except TimeoutError as e:
    raise ConnectionError("Database unavailable.") from e

This way, the traceback tells the whole story. You get the new ConnectionError, but you also see the TimeoutError that caused it. 

You can also suppress the original error (which you usually shouldn’t) like this:

raise ConnectionError("Just this error, nothing else.") from None

But unless you’ve got a good reason, keeping the full chain helps everyone understand what went wrong and how it snowballed.

You can learn about the fundamentals of logging in Python from our Logging in Python Tutorial

Real-World Examples and Use Cases

It’s one thing to talk about exception handling in the abstract, but it clicks when you see it in actual code.

Take user input, for example. Ask anyone who’s ever built a command-line tool or form validator: users will enter the weirdest things. You ask for a number? Someone will type “twelve.” Or paste in a phone number. Or just press Enter. It happens.

Instead of writing a long list of “what if” checks, you can do this:

while True:
    user_input = input("Enter a number: ")
    try:
        number = int(user_input)
        break
    except ValueError:
        print("Try again with a whole number.")

This code doesn’t panic when it gets garbage input. It tells the user to try again and loops until things look good. Much cleaner than stacking if statements for every edge case.

Here’s another one: reading from a file that might not exist.

try:
    with open("config.json") as f:
        settings = f.read()
except FileNotFoundError:
    print("Missing config file. Using defaults.")
    settings = "{}"

No need to check if the file’s there. Just try to open it, and if it fails, move on. If you’d tried checking beforehand (os.path.exists()), someone else might’ve deleted the file between the check and the open. That’s a race condition, not something you want to debug.

Network requests are another goldmine for exceptions. You can’t always trust the internet to behave. Servers go down. Connections drop. DNS fails. So if you’re doing something like this:

import requests

try:
    response = requests.get("https://example.com/data")
    response.raise_for_status()
except requests.exceptions.RequestException as e:
    print("Network problem:", e)

That RequestException base class conveniently catches pretty much anything requests might raise, from timeouts to bad responses. You don’t have to write ten different except blocks unless you want to handle them differently.

And if you’re writing automation scripts or backend services, wrapping key logic in try‑except blocks can mean the difference between one task failing and the whole system going dark. You want errors logged, recoverable tasks retried, and unrecoverable ones shut down cleanly, not with cryptic stack traces scrolling endlessly across your logs.

Learn about Python automation, including fundamental concepts, key libraries, working with data, using AI enhancements, and best practices from our Python Automation: A Complete Guide tutorial. 

Try-except Python Advanced Uses

By now, you've probably seen how flexible try‑except can be. But flexibility cuts both ways. It’s easy to go from helpful to sloppy without meaning to. Here’s how to keep things in check.

Catch specific exceptions. If you know what could go wrong, name it:

try:
    result = int(data)
except ValueError:
    # Only catches invalid numbers, not everything else under the sun

Avoid bare except:. Don’t do this unless you’re handling something very special. It’ll catch things like KeyboardInterrupt, SystemExit, and other things you probably don’t want to silence.

Use else and finally when they make the code clearer. Don’t force them in just because they exist. If the normal path of your code is getting buried inside a try, maybe move it to an else.

Keep your try blocks small. The more you include, the harder it is to figure out which line caused the error. Wrap just the part that might fail. 

Log errors when needed, especially in production. Even if you're not crashing, knowing what failed (and when) makes debugging so much easier later.

Custom exceptions aren’t mandatory, but they help. If you’ve got app-specific problems, define your own errors. They can make logs more readable and your code more self-explanatory.

One last thing: don’t use exceptions for flow control unless there’s no better way. It’s tempting to write logic like “try this; if it fails, do that,” but if it’s something you expect to happen all the time, there’s probably a cleaner way.

Conclusion

As I’ve explained throughout this article, exception handling isn’t just about avoiding crashes. It’s about writing code that expects trouble, handles it without drama, and keeps going, or shuts down in a way that makes sense. It’s the difference between a user getting a helpful message and getting dumped back to the terminal with a long, unreadable traceback.

I highly recommend learning more about exceptions, with some hands-on exercises, in our Catching Exceptions in Python chapter of our OOP in Python course.

Python try-except FAQs

What’s the difference between an error and an exception in Python?

An error usually refers to a problem that prevents Python from even starting your code, like a missing colon or a typo in a keyword. These are syntax errors. An exception occurs while the code is running, such as trying to divide by zero or opening a file that doesn't exist. Exceptions can be caught and handled so your program doesn’t crash.

Is it bad to use a bare except in Python?

Yes, in most cases. A bare except catches everything, including stuff you didn’t mean to catch, like keyboard interrupts or system exit signals. That makes debugging tough. It’s better to catch specific exceptions, such as ValueError or FileNotFoundError, so you know exactly what you're handling.

When should I use else in a try-except block?

Use else when you want to run some code only if no exception occurred in the try block. It helps keep your success-path logic separate from the error-handling logic, which can make your code easier to read and maintain.

What’s the point of finally if I already have an except block?

finally runs no matter what, whether there’s an error or not. It’s perfect for cleanup: closing files, releasing resources, rolling back transactions, and so on. Even if an error happens or you exit early, finally will still run.

Should I always use `try-except` instead of checking conditions first?

Not always, but often it’s better to just try the thing and catch the error if it fails. Python developers call this EAFP,“Easier to Ask Forgiveness than Permission.” It’s faster and avoids certain bugs, especially when something might change between the check and the action (like a file being deleted).


Derrick Mwiti's photo
Author
Derrick Mwiti
Topics

Top DataCamp Courses

Course

Introduction to Testing in Python

4 hr
21K
Master Python testing: Learn methods, create checks, and ensure error-free code with pytest and unittest.
See DetailsRight Arrow
Start Course
See MoreRight Arrow
Related
Data Skills

blog

6 Python Best Practices for Better Code

Discover the Python coding best practices for writing best-in-class Python scripts.
Javier Canales Luna's photo

Javier Canales Luna

13 min

Tutorial

Exception & Error Handling in Python

Errors and exceptions can lead to program failure or unexpected behavior, and Python comes with a robust set of tools to improve the stability of the code.
Abid Ali Awan's photo

Abid Ali Awan

Tutorial

Python KeyError Exceptions and How to Fix Them

Learn key techniques such as exception handling and error prevention to handle the KeyError exception in Python effectively.
Javier Canales Luna's photo

Javier Canales Luna

Tutorial

Python Modules Tutorial: Importing, Writing, and Using Them

Learn how to create and import Python modules. Discover best practices, examples, and tips for writing reusable, organized, and efficient Python code!

Nishant Kumar

Tutorial

30 Cool Python Tricks For Better Code With Examples

We've curated 30 cool Python tricks you could use to improve your code and develop your Python skills.
Kurtis Pykes 's photo

Kurtis Pykes

Tutorial

Python User Input: Handling, Validation, and Best Practices

Master the fundamentals of user input handling in Python, from basic prompts to advanced validation and error handling techniques. Learn how to manage secure, multiline inputs and ensure your programs are resilient and user-friendly.
Benito Martin's photo

Benito Martin

See MoreSee More