Python Error Handling: Why Exceptions Matter
In the previous post we wrote functions — clean, reusable pieces of logic. But functions assume that the inputs they receive are sensible, the files they open exist, and the network they query is available. In the real world, none of these things are guaranteed. Users make typos. Files get deleted. APIs go down. Disks fill up.
The question is not whether your program will encounter an error. It will. The question is whether it will crash with a cryptic message and lose all its work, or handle the situation gracefully and tell you — or the user — what actually went wrong.
Python’s exception system is your answer. And our birds, as patient as they are, are about to misbehave.
What Is an Exception in Python?
A Python exception is an object that represents an error detected during execution. When Python encounters a problem it cannot resolve, it raises an exception that represents what went wrong. If nothing catches it, the program stops and prints a traceback. You have certainly seen this before:
wing_spans = {"Eagle": 200, "Pigeon": 50}
print(wing_spans["Albatross"])
The KeyError is an exception. Python raises it because you asked for a key that does not exist. Without any error handling, the program stops here. That is sometimes acceptable in a quick script. It is never acceptable in production code, a shared tool, or anything a user touches.
Python try and except: Catching Exceptions
The try/except block is how you catch exceptions and decide what to do about them:
wing_spans = {"Eagle": 200, "Pigeon": 50}
def get_wingspan(bird_name: str) -> int | None:
try:
return wing_spans[bird_name]
except KeyError:
print(f"Sorry, no wingspan data for '{bird_name}'.")
return None
print(get_wingspan("Eagle"))
print(get_wingspan("Albatross"))
200
Sorry, no wingspan data for 'Albatross'.
None
The code inside try runs normally. If an exception matching the type in except is raised, execution jumps to the except block. The program continues rather than crashing.
Catching Specific Exceptions
You should always catch the most specific exception you can. Catching everything with a bare except: or except Exception: is tempting but dangerous — it swallows errors you did not anticipate, including genuine bugs, making them very hard to find later.
def load_bird_data(filepath: str) -> dict:
try:
with open(filepath, "r") as f:
import json
return json.load(f)
except FileNotFoundError:
print(f"File not found: {filepath}")
return {}
except json.JSONDecodeError as e:
print(f"Could not parse JSON in {filepath}: {e}")
return {}
Two different exceptions, two different messages. A FileNotFoundError means the path is wrong; a json.JSONDecodeError means the file exists but the contents are malformed. Treating them identically would obscure which problem you actually have.
The as e syntax gives you access to the exception object itself, which usually has a helpful message.
The try/except else Clause
Few beginners know about else on a try block, which is a shame because it is genuinely useful. The else block runs only if no exception was raised — meaning “the thing succeeded”:
def read_flock_size(filepath: str) -> None:
try:
with open(filepath, "r") as f:
count = int(f.read().strip())
except FileNotFoundError:
print("Flock file not found.")
except ValueError:
print("Flock file does not contain a valid number.")
else:
# Only runs if try succeeded completely
print(f"Flock size loaded successfully: {count} birds.")
Without else you would have to put print(...) inside the try block, which means it would also be guarded by the exception handling — slightly misleading, because the print itself cannot raise FileNotFoundError or ValueError. The else clause keeps the “success path” separate and clear.
finally: Cleanup That Always Runs
🔒 Subscribe to keep reading.
raise: Raising Exceptions in Python
🔒 Subscribe to keep reading.
Custom Exception Classes in Python
🔒 Subscribe to keep reading.
Defensive Programming Example: A Bird Registry with Exception Handling
🔒 Subscribe to keep reading.
Common Python Exceptions and How to Handle Them
🔒 Subscribe to keep reading.
Conclusion: Writing Robust Python Error Handling
🔒 Subscribe to keep reading.
References
🔒 Subscribe to keep reading.
You've hit a Deep Dive tutorial.
I spend dozens of hours researching, coding, and breaking things to write these guides. This content is free, but reserved for my subscriber community. Drop your email below to unlock this guide (and all past/future deep dives):
Full content temporarily unavailable — refresh in a moment
Already a subscriber? Use the magic link from your last newsletter, or reset your password.
Log in to unlock
New subscribers get an inbox mail: Set a password to unlock articles. The form does not log you in — use the same email afterwards.