Why this matters
Some failures are not bugs but facts of life: users type "twelve" where you asked for a number, files go missing, networks drop, a key is not in a dict yet. A robust program expects these and responds sensibly: ask again, use a default, skip the bad row and report it, clean up and exit with a clear message. Python's try statement is how you do that. Used well, it makes programs resilient; used carelessly (catching everything, everywhere), it hides real bugs. This lesson covers the full statement and the judgment about what to catch.
The basic shape
try:
n = int(input())
except ValueError:
print("That was not a whole number.")
n = 0
print("n is", n)
How it executes:
- Run the
tryblock. - If no exception happens, skip the
exceptblock entirely and continue after the statement. - If an exception happens, abandon the rest of the
tryblock at that point. If the exception's type matches theexceptclause (the class itself or a subclass), run the handler, then continue after the statement. The exception is now handled; the program goes on normally. - If it does not match, the exception keeps propagating up the call stack, as if the
trywere not there.
$ echo twelve | python3 main.py
That was not a whole number.
n is 0
Underneath, CPython does not check for errors after every operation in your code. When it compiles a try block, it records an entry in an exception table attached to the bytecode, saying "if an exception is raised between these instructions, jump to this handler". When something raises, the VM consults the table for the current frame; if no entry covers the failing instruction, it pops the frame and checks the caller's table. Entering a try block therefore costs essentially nothing (Python 3.11 made this "zero-cost") when no exception occurs.
Getting the exception object: as
except SomeError as e: binds the exception object to a name for use in the handler:
try:
value = int("3,5")
except ValueError as e:
print("Could not convert:", e)
print("Type:", type(e).__name__)
Could not convert: invalid literal for int() with base 10: '3,5'
Type: ValueError
str(e) (what print shows) is the message. Note that Python deletes the name e at the end of the handler, to avoid keeping the traceback's frames alive in memory. If you need the exception later, assign it to another name inside the handler.
Several handlers, and tuples
A try can have several except clauses. They are tried top to bottom; the first match wins, and only one handler runs:
def parse_ratio(text):
try:
a, b = text.split("/")
return int(a) / int(b)
except ValueError:
return "need two whole numbers like 3/4"
except ZeroDivisionError:
return "denominator cannot be zero"
print(parse_ratio("3/4")) # 0.75
print(parse_ratio("3/0")) # denominator cannot be zero
print(parse_ratio("3")) # need two whole numbers like 3/4
print(parse_ratio("a/b")) # need two whole numbers like 3/4
"3".split("/") gives ["3"], and unpacking one item into a, b raises ValueError: not enough values to unpack, so the same handler covers two different problems. To handle several types identically, give a tuple:
except (KeyError, IndexError) as e:
Because handlers match subclasses, order matters: put specific exceptions before general ones. An except Exception: first would swallow everything, and the specific handlers below it would be dead code.
else: code that runs only on success
The else block runs if the try block finished without an exception:
try:
n = int(text)
except ValueError:
print("bad number")
else:
print("square is", n * n)
Why not just put the print inside the try? Because then a bug in that code that happened to raise ValueError would be caught by a handler written for the conversion, and reported as "bad number". Keep the try block as small as possible: only the operation you expect might fail. Everything that depends on its success goes in else.
finally: code that always runs
The finally block runs no matter how the try statement is left: normally, by an exception that is handled, by one that is not handled (it runs, then the exception continues propagating), or by return, break or continue:
def risky():
print("working")
raise RuntimeError("boom")
try:
risky()
finally:
print("cleanup")
working
cleanup
Traceback (most recent call last):
...
RuntimeError: boom
finally is for cleanup that must happen: releasing a lock, closing a connection, removing a temporary file, restoring a setting. (For files, the with statement in lesson 4 does this for you, and is preferred.)
The full statement, with every part optional except try and at least one except or finally:
try:
<code that might fail>
except SomeError as e:
<handle that kind of failure>
else:
<runs only if no exception>
finally:
<always runs, last>
Catch narrowly
The most important judgment in exception handling: catch only what you expect and can deal with.
# Bad: hides every bug in the block
try:
total = compute_total(orders)
except:
total = 0
A bare except: catches everything, including KeyboardInterrupt (Ctrl-C stops working) and SystemExit, and also every typo: a NameError from misspelling ordres silently becomes "total is 0". except Exception: is slightly better (it lets Ctrl-C through) but still hides bugs. Name the specific exception you anticipate:
try:
total = compute_total(orders)
except KeyError as e:
print(f"order is missing field {e}")
total = 0
Reasonable places for a broad except Exception exist, for example the top level of a long-running server that must log any failure and keep serving the next request. Even then, log the full error rather than discarding it.
Re-raising
Sometimes you want to react to an error (log it, add context, clean up) but still let it propagate. A bare raise inside a handler re-raises the exception currently being handled, with its original traceback:
try:
config = load_config(path)
except OSError:
print(f"could not read {path}", file=sys.stderr)
raise
The caller still sees the original FileNotFoundError (or whatever it was), with the full traceback pointing at the real cause.
EAFP versus LBYL
Two styles exist for dealing with things that might not be there:
# LBYL: Look Before You Leap
if key in table:
value = table[key]
else:
value = None
# EAFP: Easier to Ask Forgiveness than Permission
try:
value = table[key]
except KeyError:
value = None
Python culture often prefers EAFP, especially when the check and the action could disagree (a file could be deleted between os.path.exists(path) and open(path), a classic race condition). For simple dict lookups table.get(key) beats both. Use the clearest option; just know that exceptions in Python are a normal control mechanism, not something reserved for catastrophes.
Common mistakes
A try block that is too big. Fifty lines inside try with except ValueError: at the end: you no longer know which line failed. Wrap only the risky call and use else.
Swallowing errors silently. except ValueError: pass throws away the information. At least log or count what was skipped.
Catching the wrong type. int("x") raises ValueError, not TypeError. int(None) raises TypeError. When unsure, trigger the error in the REPL and read the type off the last line of the traceback.
General before specific. except Exception: followed by except KeyError: means the second never runs. Python does not warn you.
Returning from finally. A return inside finally discards any exception in flight, silently. Python 3.14 starts warning about this; just do not do it.
Recap
try/exceptruns the block; on an exception it jumps to the first matching handler (matching includes subclasses), and the program continues after the statement.except E as egives you the exception object;str(e)is the message. A tuple catches several types.elseruns only when no exception occurred; keep thetryblock minimal and put follow-up code inelse.finallyalways runs, on every exit path; use it for cleanup.- Catch narrowly, specific before general; avoid bare
except:. A bareraisere-raises the current exception.
Going further: read the tutorial chapter "Errors and Exceptions" in the Python documentation, and in the REPL run import dis; dis.dis("try:\n x\nexcept NameError:\n pass") to see the ExceptionTable section of the bytecode.