Definition
In Python an error is fatal by default. As soon as an operation fails, the interpreter stops everything, prints a traceback, and the rest of the program never runs. That behaviour is a service while you are writing code, when you want to hear about a problem as early as possible.
It becomes a liability once the program is in someone else's hands. A file holds one malformed line out of ten thousand, a user types text where a number was expected, a remote server takes too long to answer: those failures are predictable, they are not bugs, and shutting everything down is out of proportion.
except answers exactly that need. It catches an exception raised in the try block before it, announces the type of error it knows how to handle, and its block runs only when the error that occurred matches that type.
What that changes for you: the error did happen, but you are the one deciding what it becomes. The program then carries on, and that is the whole difference between an incident and a shutdown. In the example below, an unreadable entry no longer brings the program down, it triggers a fallback value.
try:
quantity = int(entry) # fails when the entry is not a number
except ValueError:
quantity = 1 # fall back on a safe value
print("Value not understood, quantity set to 1")If the conversion succeeds, the except block is skipped: it costs nothing in the normal case. If it fails, execution jumps straight to the first line of the except, and the remaining lines of the try are abandoned.
Always name the error
Python also accepts an except with no type at all, written as a bare except:. The temptation makes sense: one line, and nothing ever crashes again.
The problem is that it catches absolutely everything. The errors you anticipated, but also keyboard interrupts, and above all your own programming mistakes.
# Disastrous: a typo becomes invisible
try:
total = price * qauntity # "qauntity" exists nowhere
except:
total = 0 # swallowed without a word
# Targeted: only what was expected gets caught
try:
total = price * quantity
except TypeError:
total = 0In the first case the NameError caused by the typo is swallowed just like a legitimate error. The total is zero, documents go out with a wrong amount, and nothing in the logs will say why. The program does not crash: it lies, which is far more expensive to diagnose than a clean stop.
Naming the type separates two very different intentions. On one side, knowing that a specific operation can fail and planning what comes next. On the other, simply not wanting to see errors any more. Only the first one is error handling.
Several blocks, and order matters
A single try can fail in several ways, and each cause often deserves its own answer. You then chain several except blocks one after the other. They are read top to bottom: the first matching type wins, its block runs, the others are ignored.
That leaves the question of what matching really means. Exceptions form a hierarchy: KeyError and IndexError both descend from LookupError, which itself descends from Exception. Catching a parent class therefore catches all of its children too, often without you having planned for it.
A broad case placed before a specific one makes the second unreachable: the block is there, it will never run, and Python issues no warning at all. Always go from the most specific to the most general.
Here are the four forms you will meet most often, ordered from the most targeted to the widest.
| Written | What it catches |
|---|---|
except ValueError: | That type only |
except (ValueError, TypeError): | Both, same handling |
except LookupError: | KeyError and IndexError together |
except Exception: | Nearly everything, a last resort only |
Getting the details
Catching the error is enough to carry on, not to understand. A message written by hand inside the except tells you that a line failed, never which one or why, and that is exactly what you miss the day the problem comes back.
The as keyword settles that point. It makes the exception object available under a name, and that object holds the exact detail, including the offending value.
except ValueError as error:
log(f"Line {number} skipped: {error}")Then comes the next question: what do you do when local handling is not enough, when you want a record of what happened without pretending you fixed it? An except can re-raise what it caught with a bare raise, once the error has been logged. It carries on up towards the caller, and the original traceback is preserved intact.
An except only protects the try it is attached to. An error raised inside the except block itself is caught by nobody and travels up as usual.
Frequently asked questions
Should Exception be caught as a last resort?
Only at the boundary of a program, for instance around a background task that must never stop, or a web request that has to answer something rather than nothing. And on one condition: log the full error, traceback included. An except catching everything without writing anything is indistinguishable from a bug.
What happens when no except matches?
The exception carries on up as if the try were not there, until it finds a block higher up that can handle it, or until the program halts. The finally block does still run on the way through, which leaves time to close a file or a connection cleanly.
Can an except hold a return?
Yes, and it is a common way to hand back a fallback value. Just make sure the caller can tell that fallback from a normal result: returning zero both when the computation failed and when it genuinely is zero makes the error invisible. An explicit None removes the ambiguity.