PY-01-015-P · Primer — Exceptions & try / except
The idea in one sentence
An exception is Python's way of stopping everything and shouting "I can't do this!" when something goes wrong at runtime, and try / except lets you catch that shout so your program can recover gracefully instead of crashing.
Why it exists
Some errors can't be spotted until the program is actually running with real data: a user types "twelve" where you expected a number, a file you tried to open isn't there, you divide by a value that turned out to be zero. Python can't fix these for you, so when one happens it raises an exception — it halts the current line, unwinds through your function calls, and prints a traceback (the "top-of-stack" report that shows what failed and where). Without a way to intervene, any single bad input would kill the whole program. try / except exists so you can wrap the risky code, say "if this specific thing goes wrong, here's what to do instead," and keep running — logging the problem, asking the user to retry, or falling back to a default. It turns an abrupt crash into a decision you get to make.
How it works
You put the code that might fail inside a try: block, and the recovery code inside an except SomeError: block; if the try code raises an exception matching that type, Python jumps straight to the except block instead of crashing. Naming the type — except ValueError rather than a bare except — means you only catch the failure you actually expected and let genuine bugs surface. Two optional clauses complete the picture: else: runs only when the try block succeeded (no exception), and finally: runs no matter what — success, failure, or even an unhandled error on its way out — which makes it the place for cleanup like closing a file. You can also trigger an exception yourself with raise, for example to reject invalid input. Here is the whole shape in one runnable example:
def parse_age(text):
return int(text) # int("twelve") will raise ValueError
try:
age = parse_age("twelve") # risky step
print("You are", age)
except ValueError: # only catches ValueError
print("That was not a whole number.")
else:
print("Parsed successfully.") # runs only if try succeeded (it didn't)
finally:
print("Done checking.") # always runs
# Output:
# That was not a whole number.
# Done checking.
The one gotcha
Catch the specific exception you expect — a bare except: (or the too-broad except Exception:) silently swallows bugs you never meant to hide. Say you meant to catch a bad number but a typo slips in:
data = {"count": "5"}
try:
n = int(data["cnt"]) # typo "cnt" -> raises KeyError, not ValueError
except ValueError:
n = 0 # this does NOT run; KeyError is a different type
# The KeyError escapes here and shows a traceback -> you see the real bug
Because you named ValueError, the unrelated KeyError from the typo is not caught — Python prints its traceback and you immediately see the true problem. Had you written a bare except: that assigned n = 0, the typo would have been hidden, n would quietly be wrong, and you'd chase a phantom bug later. The rule: name the narrowest exception that fits, so recovery handles only what you planned for and real mistakes stay loud. To also raise your own, use raise ValueError("age cannot be negative") — and add as err to inspect the caught object: except ValueError as err: print(err).
See it — how Python routes a raised exception
flowchart TD
A["Run the<br/>try: block"] --> B{"Exception<br/>raised?"}
B -->|"No"| C["Run else:<br/>block"]
B -->|"Yes"| D{"Type matches<br/>an except?"}
D -->|"Yes"| E["Run that<br/>except: block"]
D -->|"No"| F["Print traceback,<br/>crash upward"]
C --> G["Run finally:<br/>always"]
E --> G
F --> G
G --> H["Continue<br/>program"]
The one-line takeaway
Wrap risky code in try, recover from the specific exception in except, put success-only code in else, always-run cleanup in finally, and use raise to signal errors yourself.
Where you'll meet it
Exception handling shows up the instant your program touches the outside world. Reading files (FileNotFoundError), calling a web API (network and timeout errors), converting user input (ValueError), and looking up dictionary keys or list indexes (KeyError, IndexError) are all places where "it worked in testing" meets "reality is messier." You'll see finally (and its cousin the with statement) used to guarantee files and network connections get closed even when something fails halfway through. And you'll raise your own exceptions in the functions you write — validating arguments and rejecting bad input early with a clear message — so that your callers get a precise, catchable error instead of a confusing crash three layers down. Learning to catch narrowly and fail loudly here is exactly the habit that separates fragile scripts from software that survives real, unpredictable data.
Named concepts: exceptions and tracebacks, try / except / else / finally, catching specific exception types, raising exceptions
