Python exception handling lets your program deal with errors instead of crashing. You put risky code in a try block and describe what to do in an except block. An optional else runs when nothing went wrong, and finally runs no matter what, which makes it the place for cleanup. You can also raise your own exceptions when something is invalid.
This guide goes through each part with real output, then covers the mistakes that hide bugs and how to write handlers you can trust.
In this guide
The short version
try:the code that might fail.except SomeError:what to do if that error happens. Catch specific errors, not everything.else:runs only if thetryblock had no error.finally:always runs, for cleanup.raise ValueError("message")signals a problem yourself.
What is an exception?
An exception is what Python creates when something goes wrong while your program runs: dividing by zero, converting text that is not a number, reading a missing key. If nothing handles it, the program stops right there and prints a traceback:
print("before")
print(10 / 0)
print("after")
Output
before
ZeroDivisionError: division by zero
“before” printed, then the crash. “after” never ran. Exception handling lets you decide what happens instead.
try and except
Wrap the risky code in try. If an exception of the named type occurs, Python jumps straight to the matching except block and carries on afterwards:
def safe_divide(a, b):
try:
return a / b
except ZeroDivisionError:
return None
print(safe_divide(10, 2))
print(safe_divide(10, 0))
Output
5.0
None
The second call would have crashed. Instead the except block ran and the function returned None.
Catching specific exceptions
You can have several except blocks, each for a different type, and use as e to get the exception object and read its message. Python picks the first one that matches:
values = ["42", "abc", None]
for v in values:
try:
print(int(v))
except ValueError as e:
print("bad value:", e)
except TypeError as e:
print("wrong type:", type(e).__name__)
Output
42
bad value: invalid literal for int() with base 10: 'abc'
wrong type: TypeError
Each input failed in a different way: "abc" is the right type but a bad value (ValueError), and None is the wrong type entirely (TypeError). Handling them separately lets you respond appropriately.
To handle several types the same way, group them in a tuple:
data = {"a": [1, 2]}
for key, idx in [("a", 0), ("b", 0), ("a", 5)]:
try:
print(data[key][idx])
except (KeyError, IndexError) as e:
print("lookup failed:", type(e).__name__)
Output
1
lookup failed: KeyError
lookup failed: IndexError
else and finally
Two more optional blocks make the flow complete. else runs only when the try block finished without an error. finally runs every time, whatever happened. Trace the order of the output below:
def demo(x):
try:
print("trying")
result = 10 / x
except ZeroDivisionError:
print("caught")
else:
print("no error, result", result)
finally:
print("cleanup")
demo(2)
print("---")
demo(0)
Output
trying
no error, result 5.0
cleanup
---
trying
caught
cleanup
With demo(2): try, then else, then finally. With demo(0): try, then the except block, then finally. Cleanup always happens. Even a return inside try does not skip it:
def f():
try:
return "from try"
finally:
print("finally still runs")
print(f())
Output
finally still runs
from try
Use finally for things that must happen regardless: closing a file, releasing a lock, disconnecting from a database. In practice, the with statement does this job more neatly for many resources.
Raising your own exceptions
Your code can also signal a problem with raise. Use a built-in type that fits (ValueError for a bad value, TypeError for a wrong type) and a clear message:
def set_age(age):
if age < 0:
raise ValueError("age cannot be negative")
return age
try:
set_age(-5)
except ValueError as e:
print("Error:", e)
Output
Error: age cannot be negative
Sometimes you want to log something and still let the error continue upward. A bare raise inside an except block re-raises the same exception:
def load(n):
try:
return int(n)
except ValueError:
print("logging the problem")
raise
try:
load("x")
except ValueError:
print("caller saw it too")
Output
logging the problem
caller saw it too
Custom exception classes
For errors specific to your application, define your own exception by inheriting from Exception. It can carry extra data as attributes (the class basics apply as usual):
class InsufficientFunds(Exception):
def __init__(self, needed):
super().__init__(f"need {needed} more")
self.needed = needed
def withdraw(balance, amount):
if amount > balance:
raise InsufficientFunds(amount - balance)
return balance - amount
try:
withdraw(100, 130)
except InsufficientFunds as e:
print(e, e.needed)
Output
need 30 more 30
Callers can now catch InsufficientFunds specifically and read needed, instead of parsing an error message.
Exceptions form a family tree. Catching a parent type catches all its children, and Exception is the parent of nearly everything you would want to handle:
print(issubclass(KeyError, LookupError))
print(issubclass(ZeroDivisionError, ArithmeticError))
print(issubclass(ValueError, Exception))
print(issubclass(KeyboardInterrupt, Exception))
Output
True
True
True
False
Notice that KeyboardInterrupt (Ctrl+C) is not an Exception, so a normal except Exception lets the user stop the program.
EAFP vs LBYL
Two styles for handling “might not exist”. LBYL (look before you leap) checks first with an if. EAFP (easier to ask forgiveness than permission) just tries and handles the failure. Python code often prefers EAFP, especially when the check and the action could be separated by a change:
d = {"a": 1}
if "b" in d:
value = d["b"]
else:
value = 0
try:
value2 = d["b"]
except KeyError:
value2 = 0
print(value, value2)
Output
0 0
Both are fine for a dictionary. EAFP shines when the check is expensive or unreliable, such as opening a file that might disappear between the check and the read.
Common built-in exceptions
| Exception | Typical cause | Example |
|---|---|---|
| ValueError | Right type, unusable value | int('abc') |
| TypeError | Wrong type for an operation | 'a' + 1 |
| KeyError | Missing dictionary key | {}['a'] |
| IndexError | List position out of range | [1, 2, 3][5] |
| ZeroDivisionError | Dividing by zero | 1 / 0 |
| AttributeError | Object has no such attribute | 'text'.foo |
| NameError | Name not defined | print(undefined_name) |
| FileNotFoundError | Opening a file that does not exist | open('missing.txt') |
Confirming which exception each of these raises
IndexError
KeyError
TypeError
ValueError
ZeroDivisionError
Common mistakes
Mistake 1: catching everything and hiding the bug
A broad except Exception (or a bare except:) that quietly returns a default can turn a real bug into a wrong answer nobody notices. Here average(None) is a programming error, but the function reports 0:
def average(nums):
try:
return sum(nums) / len(nums)
except Exception:
return 0
print(average([2, 4]))
print(average([]))
print(average(None))
Output
3.0
0
0
Catch only the exceptions you expect and know how to handle, such as ZeroDivisionError for the empty list. Let genuine bugs surface.
Mistake 2: putting too much in the try block
Keep try small: just the line that can fail. If the block is long, an exception from an unrelated line gets caught by mistake, and it is hard to tell where it came from.
Mistake 3: swallowing errors silently
An except block that just says pass makes failures invisible. At least log the error, or re-raise it.
Mistake 4: return inside finally
A return in finally overrides everything, including an exception that was on its way out. The error vanishes:
def f():
try:
raise ValueError("boom")
finally:
return "finally wins"
print(f())
Output
finally wins
Keep finally for cleanup only.
Try it yourself
Work out each answer first, then open the solution.
1. What does this print, and in what order?
try:
print(int("7"))
except ValueError:
print("bad")
else:
print("ok")
finally:
print("done")
Show answer
Output
7
ok
doneNo error occurred, so except was skipped, else ran, and finally ran last.
2. Write safe_int(text, default=0) that converts text to an integer, or returns the default if it cannot.
Show solution
def safe_int(text, default=0):
try:
return int(text)
except ValueError:
return default
print(safe_int("12"), safe_int("x"), safe_int("x", -1))
Output
12 0 -13. Which exception does each of these raise: [1, 2, 3][5], {}["a"], "a" + 1, int("a"), 1 / 0?
Show answer
The table above lists them, and the check we ran earlier confirms: IndexError, KeyError, TypeError, ValueError, ZeroDivisionError.
4. What does this print?
def f():
try:
return 1
finally:
print("cleanup")
print(f())
Show answer
Output
cleanup
1The finally block runs before the function actually hands back the value, so “cleanup” prints first.
Run these in our free Python compiler.
Frequently asked questions
What is the difference between an error and an exception in Python?
A syntax error stops your code from even being read. An exception is a problem that happens while the program runs, and you can catch and handle it with try and except.
What is the difference between except and finally?
except runs only when a matching exception occurs. finally runs every time, whether there was an exception or not, so it is used for cleanup.
When does the else block run in a try statement?
Only when the try block completed without raising an exception. It keeps the code that depends on success separate from the code being guarded.
How do I catch multiple exceptions?
Either write several except blocks, one per type, or group types in a tuple: except (KeyError, IndexError):.
Is a bare except a good idea?
No. A bare except: catches everything, including Ctrl+C and system exit, and hides bugs. Catch specific exceptions, or at most Exception where you truly need a catch-all, and log what you catch.
How do I see the full traceback of a handled exception?
Call traceback.print_exc(), or use logging.exception("message") inside the except block. Both show where the error happened.
Related reading
- Python Context Managers and the with Statement – cleanup without writing finally.
- Reading and Writing Files in Python – where FileNotFoundError shows up.
- Python Classes and Objects for Beginners – for building custom exception classes.
- 7 Python Gotchas That Trip Up Interviews – including exception traps.
Run this code in your browser
The free Upskly compiler runs Python with nothing to install. Paste the example, change it and see what happens.
Stuck on a traceback?
AI Assist works inside the Python notebook, so you can ask about an error or a concept without leaving the cell.
Test yourself
Timed questions on Exceptions and Context Managers, with an explanation for every answer.