Definition
An error nobody planned for stops a Python program cold, right where it happens. Input that is not a number, a missing file, a lost connection: the outcome is always the same, execution halts and every bit of work already done goes down with it. try exists to spare errors that can be anticipated from that fate.
The idea is easy to grasp. try opens a block of code that may raise an exception. As long as nothing goes wrong, the program moves on as if that block were not even there. The moment an error occurs inside it, execution drops the rest of the try immediately and jumps straight to the except block built to handle it.
try:
age = int(entry)
except ValueError:
print("That is not a number")
age = NoneWith these few lines, an odd piece of input no longer crashes the program: it triggers a clear message, and execution carries on with age set to None instead of stopping. That is what separates a program that breaks at the first hiccup from one that absorbs it.
Keep the block as short as possible
Once try is known, the natural temptation is to stuff as much as possible inside it, just to be safe. That is actually the most common mistake. A block that wraps thirty lines behind a single except Exception catches whatever failure happens among those thirty lines, without ever saying which one it was.
The real cost shows up in production, the day the message on screen is a plain "Error", while the actual cause could be a missing file, a division by zero, or a misspelled variable name. These problems get the same silent answer, and fixing the bug means rereading the whole block to guess what actually broke.
# Too wide: no idea what failed
try:
data = load(path)
result = compute(data)
store(result)
except Exception:
print("Error")
# Targeted: we know, and we can react
try:
data = load(path)
except FileNotFoundError:
data = default_values()In practice, that comes down to one rule: one risky operation per block, caught by a precisely named exception. That is what lets the targeted version know that only the loading step can fail, and offer a default value without guessing whether the problem came from the computation or the storage step.
A bare except: with no exception name at all goes even further: it also catches deliberate interruptions, such as a Ctrl+C or a call to sys.exit(). The program then refuses to stop when asked to, which looks exactly like it has frozen.
The four blocks
try never works alone. Three other blocks can join it, each reserved for a specific moment in execution. The table below lays them out, before the two most useful ones get a closer look right after.
| Block | When it runs |
|---|---|
try | Always, up to the first error |
except | Only if the exception matches |
else | Only if no exception occurred |
finally | In every case, error or not |
The else block is the least known of the four, yet it solves a real problem: where should the rest of the processing go, the part that should only run once everything succeeded? The answer is to put it in else, which keeps the try down to the single line that is actually risky.
Nothing stops several except blocks from following a single try, each aimed at a different exception. Python reads them in the order they are written and stops at the first match, a detail with real consequences for how they should be ordered, covered in the next section.
Several recoveries, from precise to general
An API call, for instance, can fail in several distinct ways, and each one calls for a different response. A timeout is not handled like a network outage, itself different from a response that arrives but cannot be read. Chaining several except blocks after a single try makes it possible to answer each case on its own terms, instead of lumping them into one vague recovery.
try:
response = call_api(url)
except TimeoutError:
response = cache.read(url) # the service is slow
except ConnectionError:
response = None # the network is down
except ValueError as error:
log(f"Unreadable response: {error}")
response = NonePython reads these blocks top to bottom and stops at the first one matching the exception actually raised. That means the order is not a matter of taste: a broad case placed first would silently absorb every more specific case that follows it, and code written for TimeoutError would never run, with no warning at all.
When two different exceptions call for exactly the same handling, there is no need to duplicate the block: they can be grouped with a tuple, for instance except (TimeoutError, ConnectionError):. This spares repeating the same response twice for two similar causes.
Frequently asked questions
Can try be used inside a loop?
Yes, and it is even the most common pattern in batch processing. A faulty row gets skipped and logged, while the loop carries on with the next ones instead of stopping everything. A try block that ends up raising nothing costs practically no execution time.
How can the details of the caught error be read?
By writing except ValueError as error:, which makes the exception object available under that name inside the block. Printing or logging it gives the exact message Python provides, far more useful for debugging than a plain "an error occurred" that helps nobody.
Should everything be checked upfront instead of using try?
Python recommends the opposite: attempt the operation, then catch the failure if one occurs. Checking that a file exists before opening it leaves a window of time during which it can vanish between the check and the opening, whereas try covers the operation itself, without leaving that gap open.