Exceptions and try/except
Handle problems without crashing
In this lesson
An exception is an error Python raises while your program runs — like a missing file, or letters typed where you wanted a number. Exception handling with try/except lets your program catch these and respond gracefully instead of crashing.
Explain it like I’m 5
A try/except is a safety net. You ‘try’ something risky, and if it goes wrong, the ‘except’ catches the fall and decides what to do instead of letting the program crash.
try and except
Put risky code in a try block. If it raises an exception, Python jumps to the matching except block instead of crashing: try: number = int(text) / except ValueError: print("Not a number"). If nothing goes wrong, the except is skipped. This lets you plan for failure at a specific spot.
def safe_divide(a, b):
try:
return a / b
except ZeroDivisionError:
return "Cannot divide by zero"
print(safe_divide(10, 2))
print(safe_divide(10, 0))
5.0 Cannot divide by zero
Wrap the conversion in try/except so that invalid input prints 'Please enter a number' instead of crashing. Test it with the given non-numeric value.
text = "abc"
number = int(text)
print("You entered", number)
Put the int() call inside try: and handle ValueError in the except.
text = "abc"
try:
number = int(text)
print("You entered", number)
except ValueError:
print("Please enter a number")
Please enter a number
Catch specific exceptions, not everything
Name the exception you expect: except ValueError:, except FileNotFoundError:. A bare except: catches everything, including bugs and typos you’d rather see — it hides problems. Catch the narrowest exception that makes sense, so genuine surprises still surface.
values = ["10", "x", "30"]
total = 0
for v in values:
try:
total += int(v)
except ValueError:
print("Skipping:", v)
print("Total:", total)
Skipping: x Total: 40
else and finally
else runs only if the try succeeded with no exception. finally runs no matter what — success, failure, even if you return — which makes it the place for cleanup like closing a file. Together they let you separate ‘the risky part’, ‘what to do on success’, and ‘what to do always’.
try:
n = int("10")
except ValueError:
print("Not a number")
else:
print("Parsed successfully:", n)
finally:
print("Done trying.")
Parsed successfully: 10 Done trying.
Raising your own, and when to let it crash
You can signal a problem with raise ValueError("price must be positive"). But don’t catch exceptions you can’t meaningfully handle — if there’s nothing sensible to do, letting the program crash with a clear traceback is better than swallowing the error and continuing in a broken state.
def set_price(amount):
if amount < 0:
raise ValueError("price must be positive")
return amount
print(set_price(10))
print(set_price(-5))
10 ValueError: price must be positive
Catching several types, and inspecting the error
One except can handle several exception types at once — list them in parentheses: except (ValueError, TypeError):. You can also capture the exception object with as e, which lets you read its message, log it, or check exactly which error occurred with type(e).__name__.
This keeps related failures in one tidy handler while still letting you react to the specifics.
for value in ["42", "oops", None]:
try:
print("Parsed:", int(value))
except (ValueError, TypeError) as e:
print("Skipped", repr(value), "-", type(e).__name__)
Parsed: 42 Skipped 'oops' - ValueError Skipped None - TypeError
Common mistake: Catching and silently ignoring exceptions
Writing except: pass makes errors disappear, which feels like ‘fixing’ them but actually hides bugs and bad data.
Catch the specific exception and do something meaningful — log it, use a default, or inform the user. Only ignore an exception when that’s genuinely the right behaviour.
Common mistake: Wrapping too much code in one try
A giant try block makes it unclear which line might fail and can catch errors you didn’t intend to.
Keep try blocks small — ideally around the single risky operation — so the handling is precise.
What does a try/except block let you do?
It lets you catch and handle expected errors gracefully; it doesn’t stop errors from occurring.
Why prefer except ValueError: over a bare except:?
Catching a specific type avoids hiding unrelated bugs and typos.
Mini exercise (medium)
Write to_int(text) that returns the integer value of text, or 0 if it can’t be converted. Test it with "15" and "oops".
Do it here. Finish the code below and press Run to try your answer right in the browser — no setup needed.
def to_int(text):
# TODO: return int(text), or 0 if it cannot be converted
return 0
print(to_int("15"), to_int("oops"))
Try return int(text) and catch ValueError to return 0.
def to_int(text):
try:
return int(text)
except ValueError:
return 0
print(to_int("15"), to_int("oops"))
15 0
assert to_int("15") == 15 and to_int("oops") == 0, "to_int of 15 is 15 and to_int of oops is 0"
print("✓ Handled both!")