What you'll learn
- Catch a specific exception with
try/exceptand let everything else through - Use
elseandfinallyfor code that runs on success and code that runs no matter what - Raise your own exceptions with
raiseand choose the right built-in type - Define a small exception hierarchy for your own program
Concept
An exception is Python's way of saying "this line cannot continue". Until now you have let exceptions end the program and read the traceback. In real programs, some failures are expected: a user types a word where a number belongs, a file is missing, a network request times out. try/except lets you name the failure you expect, handle it, and carry on. Everything you did not name still propagates, which is what you want: an unexpected error should be loud.

The single most important rule is to catch specific exceptions. except ValueError: says "I know what to do when the text is not a number". A bare except: or except Exception: catches everything, including bugs in your own code and the KeyboardInterrupt from Ctrl+C, and hides them. Since Python 3.14 you can list several types without parentheses, except ValueError, TypeError:, and except ValueError as err: gives you the exception object to inspect or log.
Keep the try block small: one operation that can fail, not the whole function. Code that depends on that operation succeeding goes in else, so an exception from it is not mistaken for the one you were catching. finally runs whether or not anything went wrong and even if the block returns, which makes it the place for cleanup; with is a shorter way to get the same guarantee for files and locks.
| Clause | Runs when | Typical use |
|---|---|---|
try |
always, first | the one operation that might fail |
except X |
an X (or subclass) was raised in try |
recover, retry, report |
else |
try finished with no exception |
the follow-up that needs the result |
finally |
always, last, even after return |
close, unlock, clean up |
Your own code should raise exceptions too. When a function receives an argument it cannot work with, raise ValueError("amount must be positive") is the honest response; returning -1 or None and hoping the caller checks is how bugs hide. Pick the built-in type whose meaning matches: ValueError for a bad value, TypeError for a wrong type, KeyError or LookupError for a missing thing, RuntimeError when nothing more specific fits.
For a program of any size, define your own exception types. A class with a pass body that inherits from Exception is enough: class PaymentError(Exception): pass. Callers can then catch your program's failures without catching unrelated ones, and a small hierarchy, a base class with a few specific subclasses, lets them choose how precisely to catch. You will learn classes fully in a few lessons; this pattern is simple enough to use now.

Code
Catching a specific failure and letting the program continue. Bad input is handled; the loop moves on; the summary still prints.
# catch.py
entries = ["12", "7", "twelve", "3.5", "40"]
total = 0
skipped = []
for entry in entries:
try:
total += int(entry)
except ValueError:
skipped.append(entry)
print("total:", total)
print("skipped:", skipped)
total: 59
skipped: ['twelve', '3.5']All four clauses together, with the exception object in hand. The first call succeeds and runs else; the second fails and runs except; finally runs both times.
# four_clauses.py
def read_number(text):
try:
value = int(text)
except ValueError as err:
print(" except:", err)
return None
else:
print(" else: parsed", value)
return value
finally:
print(" finally: done with", repr(text))
print(read_number("42"))
print(read_number("x"))
else: parsed 42
finally: done with '42'
42
except: invalid literal for int() with base 10: 'x'
finally: done with 'x'
NoneRaising your own, with the type that matches the problem. Callers that catch ValueError get a clear message; callers that do not get an honest crash instead of a wrong answer.
# raise_own.py
def withdraw(balance, amount):
if not isinstance(amount, (int, float)):
raise TypeError(f"amount must be a number, got {type(amount).__name__}")
if amount <= 0:
raise ValueError("amount must be positive")
if amount > balance:
raise ValueError(f"cannot withdraw {amount} from balance {balance}")
return balance - amount
print(withdraw(100, 30))
try:
withdraw(100, -5)
except ValueError as err:
print("handled:", err)
print(withdraw(100, 500))
70
handled: amount must be positive
Traceback (most recent call last):
File "raise_own.py", line 15, in <module>
print(withdraw(100, 500))
~~~~~~~~^^^^^^^^^^
File "raise_own.py", line 7, in withdraw
raise ValueError(f"cannot withdraw {amount} from balance {balance}")
ValueError: cannot withdraw 500 from balance 100A small hierarchy, and the 3.14 form of catching several types at once. Catching the base class handles both specific errors; catching KeyError, IndexError together shows the parenthesis-free syntax.
# hierarchy.py
class PaymentError(Exception):
pass
class CardDeclined(PaymentError):
pass
class InsufficientFunds(PaymentError):
pass
def charge(card, amount):
if card == "0000":
raise CardDeclined("card 0000 was declined")
if amount > 50:
raise InsufficientFunds(f"{amount} exceeds the limit")
return "ok"
for card, amount in [("1234", 20), ("0000", 20), ("1234", 90)]:
try:
print(charge(card, amount))
except PaymentError as err:
print(f"{type(err).__name__}: {err}")
data = {"a": [1]}
try:
print(data["a"][5])
except KeyError, IndexError:
print("missing key or index")
ok
CardDeclined: card 0000 was declined
InsufficientFunds: 90 exceeds the limit
missing key or indexTry it yourself
Create safe_input.py with a function ask_int(prompt) that keeps asking until the user types a valid integer, catching ValueError each time, and returns it. Then write divide(a, b) that raises a ValueError with a clear message when b is zero instead of letting ZeroDivisionError escape, and a small try/except/else/finally that calls it and prints which clauses ran.
Common mistakes
except:with nothing after it. It swallows every error including bugs and Ctrl+C; always name the exception.- A huge
tryblock. If ten lines are inside, you no longer know which one raised; keep one operation intryand put the rest inelse. - Returning a sentinel instead of raising.
-1orNonefor "failed" is silently ignored by callers;raisecannot be.
What's next
- Catch specific exceptions with
except X as err; keeptrysmall, useelsefor the success path andfinallyfor cleanup. raisethe built-in type that matches the problem; never return a sentinel to signal failure.- Define a small hierarchy of your own exceptions so callers can catch exactly what they mean.
Your programs can fail gracefully. Next you will split them across files, with modules, packages, and imports.