Python `try-except`: A Practical Error-Handling Guide
Exceptions let Python programs report failures without encoding every error as a special return value. try and except provide structured recovery when an operation has expected failure modes.
Basic try-except
try:
number = int("not-a-number")
except ValueError:
print("The value is not a valid integer.")When int() raises ValueError, Python skips the remaining code in the try block and runs the matching handler.
Catch Specific Exceptions
Prefer specific exception types:
try:
result = 10 / divisor
except ZeroDivisionError:
print("The divisor must not be zero.")A broad handler such as except Exception: can be useful at process boundaries for logging or cleanup, but using it everywhere can hide programming bugs.
Inspect the Exception
try:
with open("config.json", encoding="utf-8") as file:
content = file.read()
except OSError as exc:
print(f"Could not read the file: {exc}")The exception object contains details supplied by the operation that failed.
Multiple Handlers
Use separate handlers when recovery differs:
try:
value = int(user_input)
result = 100 / value
except ValueError:
print("Enter an integer.")
except ZeroDivisionError:
print("Enter a non-zero integer.")When several exceptions should receive the same response, group them in a tuple:
except (FileNotFoundError, PermissionError) as exc:
print(f"Cannot access the file: {exc}")else: Run Only on Success
The optional else block runs when the try block completes without raising an exception:
try:
value = int("42")
except ValueError:
print("Invalid integer")
else:
print(f"Parsed value: {value}")Keeping success-only code in else can make the protected try block smaller, which reduces the chance of accidentally catching an unrelated error.
finally: Always Run Cleanup
resource = acquire_resource()
try:
use_resource(resource)
finally:
release_resource(resource)finally runs whether the try block succeeds, raises an exception, or returns early.
For files, locks, and many other resources, a context manager is usually clearer:
with open("data.txt", encoding="utf-8") as file:
content = file.read()Raise Exceptions Deliberately
Your own code can signal invalid input with raise:
def withdraw(balance, amount):
if amount < 0:
raise ValueError("amount must be non-negative")
if amount > balance:
raise ValueError("insufficient balance")
return balance - amountChoose an existing exception type when it accurately describes the problem, or define a custom exception for domain-specific failures.
Re-Raise an Exception
Sometimes you want to add logging and then preserve the original failure:
try:
process()
except ProcessingError:
logger.exception("Processing failed")
raiseA bare raise inside an exception handler re-raises the active exception with its traceback intact.
Exception Chaining
When translating one error into another, use raise ... from ...:
try:
value = int(raw_value)
except ValueError as exc:
raise ConfigurationError("invalid retry count") from excThis preserves the causal relationship in the traceback.
Do Not Use Exceptions for Normal Branching
Exceptions are appropriate for exceptional or failure conditions. If a condition is normal and easily testable, a straightforward check is usually clearer:
if key in mapping:
value = mapping[key]That said, Python code often uses an “ask forgiveness rather than permission” style when attempting the operation directly is clearer and avoids duplicated work.
Conclusion
Catch the narrowest exceptions you can recover from, keep try blocks focused, use else for success-only work and finally for guaranteed cleanup, and preserve useful traceback information when translating or re-raising errors.