PYTHON / ERRORS AND EXCEPTIONS
try, except, and catching specific exceptions
You can catch runtime failures with try/except, target the exact exception classes you can recover from, and order handlers so specific cases stay reachable.
What you will learn
- Wrap only the lines that can fail, so unrelated bugs still surface
- Name the exception class you can recover from instead of except Exception
- Order handlers specific-to-general, because the first matching clause wins
- Bind the object with as e to read the message and type of the failure
Understanding try, except, and catching specific exceptions
When an expression raises, Python stops executing the rest of the try block immediately and looks for a handler. It walks the except clauses of that try statement in written order and stops at the first one whose class matches the raised exception, using the same rule as isinstance: a clause naming LookupError matches a KeyError because KeyError inherits from it. Nothing after the failing line inside try runs, which is why the size of the try block decides how much of your logic gets skipped on failure.
This is the reason to keep try blocks small and the exception classes narrow. If you put ten lines in one try and catch Exception, a typo that raises NameError on line eight is silently treated as the failure you were expecting, and you get a wrong answer instead of a traceback. Writing except ValueError is a claim that you know one specific thing can go wrong there and you know how to continue; writing except Exception is a claim you can recover from anything, which is almost never true.
Order matters only between a class and its subclasses. Because the first match wins and Python does not search for the closest match, an except ArithmeticError placed above except ZeroDivisionError makes the second clause unreachable for division by zero. Sibling classes such as KeyError and ValueError can appear in any order, or be grouped as one clause with a tuple: except (KeyError, ValueError). Use as e when you need the failure details, such as the offending text inside a ValueError message.
config = {"retries": "3", "timeout": "0", "mode": "fast"}
BUDGET = 90
def budget_per_retry(key):
try:
return BUDGET // int(config[key])
except KeyError:
return "no such key: " + key
except ValueError as e:
return "not a number: " + str(e.args[0])
except ZeroDivisionError:
return "value was zero"
for key in ["retries", "mode", "timeout", "workers"]:
print(key, "->", budget_per_retry(key))An except clause matches the raised exception and all its subclasses, and the first matching clause in source order wins, so name the narrowest class you can genuinely recover from.
Worked examples
A clause catches subclasses too
Shows that one except clause naming a base class handles every exception that inherits from it.
def lookup(seq, i):
try:
return seq[i]
except LookupError as e:
return type(e).__name__ + " caught by the LookupError clause"
print(lookup([1, 2, 3], 7))
print(lookup({"a": 1}, "b"))
print(issubclass(IndexError, LookupError), issubclass(KeyError, LookupError))Example explained
Line 1seq[i] raises IndexError for the list and KeyError for the dict, two different classes.
Line 2The single except LookupError clause matches both because matching follows inheritance, not exact class identity.
Line 3type(e).__name__ proves which concrete exception arrived, even though the clause named the base class.
Line 4The issubclass line is the mechanism behind that behaviour, printed explicitly.
One clause for several unrelated failures
Groups sibling exception classes in a tuple and keeps the arithmetic outside the try block.
raw = ["12", "x", "7"]
total = 0
for item in raw:
try:
n = int(item)
except (ValueError, TypeError) as e:
print("skipping", repr(item), "-", type(e).__name__)
continue
total += n * 2
print("total:", total)Example explained
Line 1The tuple form matches if the exception is an instance of any class listed, without duplicating the handler body.
Line 2Only int(item) is inside try, so a mistake in the doubling arithmetic would raise normally instead of being swallowed.
Line 3continue skips the accumulation for the failed item, which is why 'x' contributes nothing.
Line 4total is 12 * 2 + 7 * 2, so the surviving items still accumulate.
Clause order decides the winner
Demonstrates that a base class listed first makes the more specific clause unreachable.
def broad_first(n):
try:
return 10 / n
except ArithmeticError:
return "generic arithmetic failure"
except ZeroDivisionError:
return "divided by zero"
def specific_first(n):
try:
return 10 / n
except ZeroDivisionError:
return "divided by zero"
except ArithmeticError:
return "generic arithmetic failure"
print(broad_first(0))
print(specific_first(0))Example explained
Line 1ZeroDivisionError inherits from ArithmeticError, so both clauses can match the same exception.
Line 2In broad_first the ArithmeticError clause comes first and wins; the ZeroDivisionError clause below it can never run.
Line 3Python does not look for the closest matching class, so reordering the clauses is the only fix.
Line 4specific_first prints the precise message because the subclass is tested before its base.
Important notes
except Exception does not catch KeyboardInterrupt or SystemExit, because those derive from BaseException directly; that is deliberate, so Ctrl-C still stops your program.
The name bound by as e is deleted when the except block ends, so save what you need into another variable if you want it afterwards.
Common mistakes
Writing a bare except: or except Exception around a large block, which turns typos and NameError into the expected failure and produces silently wrong results instead of a traceback.
Listing a base class such as Exception or LookupError before a specific one, leaving the specific clause dead code that never runs.
Using the Python 2 form except ValueError, TypeError:, which is a SyntaxError in Python 3; the tuple needs parentheses: except (ValueError, TypeError):.
Try it yourself
Change, predict, then run
Write read_float(data, key) that returns float(data[key]), with separate except clauses for KeyError and ValueError returning distinct messages, then call it with a valid numeric string, a non-numeric string, and a missing key.
Open the Python workspaceCheck your understanding
A try block raises KeyError. Its handlers are, in order, except LookupError: then except KeyError:. Which handler runs?
- The LookupError handler, because clauses are tested top to bottom and KeyError is a subclass of LookupError
- The KeyError handler, because Python selects the most specific matching class
- Both handlers run, in the order written
- Neither; Python reports a TypeError because the two clauses overlap
Show answer
Matching is a top-to-bottom isinstance test and the first match wins, so the LookupError clause handles the KeyError and the clause below it is unreachable. The tempting answer is that Python picks the most specific class, but no such search happens; specificity only takes effect if you write the subclass clause first.