Lesson 6 of 9
An uncaught exception kills your program. try/except lets you catch it and carry on.
try:
result = 10 / 0
print("This never runs.")
except ZeroDivisionError:
print("Cannot divide by zero.")
print("Program continues.")
Python runs the try block. If an exception is raised, it jumps straight to a matching except
block; if not, except is skipped entirely.
Catching the right thing
Name the specific exception you expect:
def to_int(text):
try:
return int(text)
except ValueError:
return None
print(to_int("42"))
print(to_int("hello"))
Don't catch everything
This is the most common bad habit in error handling:
try:
result = 10 / 0
except:
print("Something went wrong")
A bare except: swallows everything — including typos in your own code, and KeyboardInterrupt
when the user tries to quit. You'll be left staring at "Something went wrong" with no idea what
broke.
Catch what you can actually handle, and let the rest crash loudly. A crash with a traceback is far more useful than a program that silently misbehaves.
Multiple except blocks
def safe_divide(a, b):
try:
return int(a) / int(b)
except ValueError:
return "Not a number"
except ZeroDivisionError:
return "Cannot divide by zero"
print(safe_divide("10", "2"))
print(safe_divide("abc", "2"))
print(safe_divide("10", "0"))
Python takes the first matching block, so order from specific to general — a broad
except Exception first would shadow everything after it.
Handle several the same way by grouping them in a tuple:
try:
value = int("abc")
except (ValueError, TypeError) as e:
print(f"Bad input: {type(e).__name__}")
as e — inspecting the exception
try:
with open("missing.txt") as f:
content = f.read()
except FileNotFoundError as e:
print(f"Could not open the file: {e}")
else
The else block runs only if no exception was raised:
def read_count(text):
try:
number = int(text)
except ValueError:
return "Not a number"
else:
return number * 2
print(read_count("5"))
print(read_count("abc"))
Why bother? It keeps the try block down to just the line that might fail. Anything in try
that can't raise the error you're catching risks catching an unrelated bug by accident.
finally
The finally block runs no matter what — exception or not, even after a return:
def process():
try:
return 10 / 0
except ZeroDivisionError:
return "handled"
finally:
print("Cleanup always runs.")
print(process())
finally is for cleanup: closing connections, releasing locks. You'll notice this is exactly
what with does for files — and with is the better tool when one exists.
A practical example
import json
def load_config(path):
try:
with open(path) as f:
return json.load(f)
except FileNotFoundError:
return {"theme": "default"}
except json.JSONDecodeError:
return {"theme": "default", "error": "corrupt file"}
print(load_config("no-such-file.json"))
with open("bad.json", "w") as f:
f.write("{not valid json")
print(load_config("bad.json"))
Two different failures, two sensible fallbacks, no crash.
Try it yourself
Handle a missing key, a bad conversion, and a division by zero.
Checkpoint
try/exceptcatches an exception so the program keeps running.- Catch specific exceptions; never use a bare
except:. - The first matching
exceptwins — order specific before general. as egives you the exception object.elseruns when nothing failed;finallyruns either way, for cleanup.
Questions about this lesson
Log in to ask a question.