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.
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/exceptstatement, and why bareexcept:is risky - Catching specific exception types, and multiple types together
- Getting the error message with
as e - The
elseandfinallyclauses, 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 zeroThat 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
tryare 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
exceptrun 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
| Exception | When it happens |
|---|---|
| ZeroDivisionError | Dividing by zero |
| ValueError | Right type, but an invalid value — e.g. int("banana") |
| TypeError | Wrong type entirely — e.g. "2" + 2 |
| NameError | Using a variable that doesn't exist |
| IndexError | Accessing a list position that doesn't exist |
| KeyError | Accessing a dictionary key that doesn't exist |
| FileNotFoundError | Trying to open a file that isn't there |
| AttributeError | Using 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
tryblock small and focused so it's obvious exactly what could fail. - Use
finallyfor cleanup — closing files or releasing resources, things that must happen either way. - Don't use exceptions to replace normal
ifchecks. 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
exceptblocks something to actually do. An emptyexcept: passsilently 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.
Type Casting in Python
Why int() and float() can raise ValueError.
Conditionals in Python (if, elif, else)
When to use if checks instead of exceptions.
The match-case Statement in Python
Default cases — the same safety-net idea as except.
Python course hub
All free Python explainers and the path into Agentic practice.
File Handling in Python (Reading, Writing, CSV & JSON)
open(), with, reading modes, CSV, and JSON.