SythraOpen app

Try/Except and Exception Handling in Python

try/except in Python lets a program catch errors like ZeroDivisionError or ValueError instead of crashing — wrap risky code in try, handle specific errors in except, use else for success-only code, and finally for cleanup that always runs.

Sythra

11 min read

XLinkedIn
Try/Except and Exception Handling in Python — cover illustration

Imagine a program that asks the user to type a number, then divides 10 by it. If the user types 0, the program explodes with a scary red error message and stops dead. If they type "banana" instead of a number, it explodes again, in a different way. Try/except and exception handling in Python is how you teach a program to expect these problems, catch them, and react calmly instead of crashing.

Until now, every error you've hit has been a bug — something you did wrong, fixed by rewriting your code. This page is about a different kind of problem: errors caused by things outside your control — bad user input, a missing file, a network that doesn't respond.

What you will learn

  • What an exception actually is, and how to read a traceback
  • The try/except statement, and why bare except: is risky
  • Catching specific exception types, and multiple types together
  • Getting the error message with as e
  • The else and finally clauses, and the order they run in
  • Raising your own exceptions with raise, including custom exception classes

What is an exception?

When Python runs into a problem it cannot recover from on its own, it doesn't just quietly fail — it raises an exception. Think of an exception like a small alarm bell going off inside your program. The moment that alarm rings, Python stops whatever it was doing and looks for someone to "catch" it. If nobody catches it, the alarm reaches all the way to the top, and your whole program crashes with a wall of red text.

x = 10 / 0
# ZeroDivisionError: division by zero

That whole message is called a traceback — Python's way of saying "here is exactly where the alarm went off, and here is what kind of alarm it was." The last line tells you two things: the type of exception (ZeroDivisionError) and the error message (division by zero).

age = int("hello")
# ValueError: invalid literal for int() with base 10: 'hello'

Python tried to turn the text "hello" into a number, couldn't do it, and rang the alarm bell — this time the exception type is ValueError. See Type Casting in Python for more on why casting can fail.

Why letting your program crash is a problem

Imagine you built a simple calculator for a friend, and somewhere in the middle of using it, they accidentally divide by zero. Without any protection, the entire program shuts down immediately — even if they were about to do five more calculations after that. One small mistake takes down everything. What we want instead is a program that says: "Oh, that didn't work. Let me tell the user nicely, and keep going as if nothing happened." That's exactly what try and except let you do.

The try/except statement: catching the alarm

try:
    risky_code_that_might_fail()
except:
    print("Something went wrong!")
  • try: — "attempt to run the code in this block; I know it might fail, so be careful"
  • The indented lines under try are the code you suspect could cause an exception
  • except: — "if anything inside that try block rings the alarm bell, come here instead of crashing the whole program"
  • The indented lines under except run only if something went wrong above
try:
    number = int(input("Enter a number: "))
    result = 10 / number
    print("10 divided by your number is:", result)
except:
    print("That didn't work. Please try a valid, non-zero number next time.")

If the user types 5, the try block runs perfectly and the except block is completely skipped. If they type 0, Python hits 10 / 0, the alarm bell rings (ZeroDivisionError), and Python jumps into the except block instead of crashing. If they type banana, the alarm rings even earlier, at int(input(...)) (ValueError), and Python jumps to the same except block. This is the entire magic trick of exception handling: wrap the risky part in try, and put your "Plan B" in except.

Catching specific exceptions

A bare except: catches any kind of error — it's like a smoke detector that goes off for smoke, dust, steam, and burnt toast, and you can never tell which one it actually was. It can also accidentally hide real bugs, because it catches everything, even mistakes you didn't expect. A much better habit is to catch the specific type of exception you expect:

try:
    number = int(input("Enter a number: "))
    result = 10 / number
    print("10 divided by your number is:", result)
except ZeroDivisionError:
    print("You can't divide by zero!")
except ValueError:
    print("That wasn't a valid number.")

Python checks each except line in order, just like elif. If a ZeroDivisionError happens, only the first block runs. If a ValueError happens, only the second block runs — your error messages stay accurate and specific.

Catching multiple exception types together

Sometimes you want the same response for more than one kind of error. Group the exception types together using parentheses:

try:
    number = int(input("Enter a number: "))
    result = 10 / number
except (ZeroDivisionError, ValueError):
    print("Please enter a non-zero number.")

This says: "if either a ZeroDivisionError OR a ValueError happens, run this same block." The parentheses are required.

Getting details about the exception with as

Sometimes you don't just want to know that something went wrong — you want to know exactly what the error message said. Grab the exception object with as:

try:
    number = int(input("Enter a number: "))
    result = 10 / number
except ZeroDivisionError as e:
    print("Error occurred:", e)
except ValueError as e:
    print("Error occurred:", e)

e is just a variable name (the common convention for "error") holding the actual exception object — printing it shows the descriptive message that came with it, like division by zero. This is incredibly useful while debugging, since you get the real, specific reason instead of a generic "something went wrong."

The else clause: code that runs only if nothing failed

try/except has an optional third part: else. This block runs only if the try block completed with no exceptions at all.

try:
    number = int(input("Enter a number: "))
    result = 10 / number
except ZeroDivisionError:
    print("You can't divide by zero!")
except ValueError:
    print("That wasn't a valid number.")
else:
    print("Success! The result is:", result)

Why not just put print("Success!...") at the bottom of the try block instead? Because else makes your intentions crystal clear: "this part only happens when everything in try went perfectly, with zero surprises." It keeps your "happy path" separated from your risky code, making bugs easier to spot later.

The finally clause: code that always runs, no matter what

Code inside a finally block runs no matter what happens — whether the try block succeeded, whether an exception was caught, or even if an exception happened that wasn't caught at all.

try:
    file = open("notes.txt", "r")
    content = file.read()
    print(content)
except FileNotFoundError:
    print("That file doesn't exist.")
finally:
    print("Done attempting to read the file.")

finally is perfect for cleanup work — things that absolutely must happen regardless of success or failure, like closing a file you opened. See File Handling in Python for the full picture.

Here's the full order of operations:

try:
    print("1. Trying...")
    x = 10 / 0
except ZeroDivisionError:
    print("2. Caught the error!")
else:
    print("This would only print if there was NO error")
finally:
    print("3. This always runs, error or not.")

# Output:
# 1. Trying...
# 2. Caught the error!
# 3. This always runs, error or not.

Raising your own exceptions

So far, Python has been ringing all the alarm bells automatically. Sometimes you want to ring the alarm yourself, on purpose, because you know something is wrong even if Python doesn't. Do this with raise:

def set_age(age):
    if age < 0:
        raise ValueError("Age cannot be negative.")
    print("Age set to", age)

set_age(-5)
# ValueError: Age cannot be negative.

This is incredibly useful for protecting your own functions — you don't have to wait for Python to naturally stumble into a problem. Whoever calls your function later can wrap it in their own try/except and handle your raised exception just like any built-in one:

try:
    set_age(-5)
except ValueError as e:
    print("Couldn't set age:", e)

Creating your own custom exception types

Python lets you invent your own exception types, useful when none of the built-ins really describe your specific problem. Create one by making a new class that inherits from Exception:

class NegativeAgeError(Exception):
    pass

def set_age(age):
    if age < 0:
        raise NegativeAgeError("Age cannot be negative.")
    print("Age set to", age)

That little three-line recipe — class SomeName(Exception): pass — is the standard way to create a brand-new, custom-named exception (full detail on classes in Classes and Objects in Python). Once made, you raise and catch it exactly like a built-in exception:

try:
    set_age(-5)
except NegativeAgeError as e:
    print("Custom error caught:", e)

NegativeAgeError is far more descriptive than a generic ValueError — anyone reading your code immediately understands exactly what kind of problem occurred, just from the name.

Common built-in exceptions you will meet often

ExceptionWhen it happens
ZeroDivisionErrorDividing by zero
ValueErrorRight type, but an invalid value — e.g. int("banana")
TypeErrorWrong type entirely — e.g. "2" + 2
NameErrorUsing a variable that doesn't exist
IndexErrorAccessing a list position that doesn't exist
KeyErrorAccessing a dictionary key that doesn't exist
FileNotFoundErrorTrying to open a file that isn't there
AttributeErrorUsing a method/property that doesn't exist on an object

You don't need to memorize all of these right now — just know they exist, and when you see one in a traceback, you'll already have a head start on understanding what went wrong.

Best practices: how to use try/except wisely

  • Don't catch everything blindly. A bare except: hides real bugs along with the errors you actually expected. Catch specific exception types whenever you can.
  • Only wrap the risky line(s), not your whole program. Keep the try block small and focused so it's obvious exactly what could fail.
  • Use finally for cleanup — closing files or releasing resources, things that must happen either way.
  • Don't use exceptions to replace normal if checks. If you can easily check for a problem in advance, do that first. Exceptions are best for situations you genuinely can't predict, like bad user input or a missing file.
  • Give your except blocks something to actually do. An empty except: pass silently swallows errors and makes bugs nearly impossible to track down later.

Common questions

What is an exception in Python?

An exception is Python's way of signaling that something went wrong that it cannot recover from automatically, such as dividing by zero (ZeroDivisionError) or converting invalid text to a number (ValueError). Uncaught exceptions crash the program.

What is the difference between try, except, else, and finally?

try holds code that might fail. except catches and handles specific exceptions if they occur. else runs only if the try block succeeded with no exceptions. finally always runs, whether or not an exception happened, and is used for cleanup.

Why shouldn't I use a bare except: in Python?

A bare except: catches every possible error, including ones you didn't anticipate, which can hide real bugs. Catching specific exception types like except ValueError: keeps error handling accurate and makes unexpected bugs visible instead of silently swallowed.

How do you raise a custom exception in Python?

Use the raise keyword with a built-in exception like raise ValueError("message"), or define your own by creating a class that inherits from Exception, like class NegativeAgeError(Exception): pass, then raise NegativeAgeError("message").

How do I see the actual error message in an except block?

Use the as keyword to capture the exception object, like except ValueError as e:, then print(e) to see the specific message Python attached to that error.

Explore

Related topics

Keep going — these sit next to this concept in a real learning path.

Browse all python explainers →