Catch Exceptions with Try Except in Python
Use targeted try/except, else, finally, and raise to prevent crashes, clean up safely, and report clear errors.

Python programs fail at runtime when operations like file access, parsing input, or network calls raise exceptions. Handling these with try
/except
keeps your app responsive, preserves useful context, and guarantees cleanup.
Before you start — core rules
- Catch the most specific exception types you expect (for example,
ValueError
,FileNotFoundError
), not a blanket catch-all. - Use
else
for the success path andfinally
for code that must run no matter what. - Prefer context managers (
with
) for files and other resources to simplify cleanup. - Let unexpected bugs propagate; log them and re-raise instead of silently swallowing them.
- Reserve catch-all handlers (
except Exception
orexcept BaseException
) for top-level boundaries where you can fail fast or report clearly.
Method 1 — Catch specific exceptions (recommended)
Step 1: Identify one risky operation to protect.
raw = input("Select a fruit number (0-2): ")
Step 2: Wrap the operation in try
/except
and handle explicit exception types.
try:
selection = ["apple", "pear", "banana"][int(raw)]
print("You chose:", selection)
except ValueError:
print("Not a number.")
except IndexError:
print("Choice out of range.")
Step 3: Group related exceptions when the recovery is identical.
try:
selection = ["apple", "pear", "banana"][int(raw)]
except (ValueError, IndexError) as e:
print("Invalid selection:", type(e).__name__)
Common specific exceptions include OSError
family (files), ValueError
and TypeError
(data), ZeroDivisionError
(math), and KeyError
/IndexError
(collections).
Method 2 — Use else and finally to separate success and cleanup
Step 1: Add else
for code that should run only if nothing failed.
try:
f = open("config.json", "r", encoding="utf-8")
except FileNotFoundError:
print("No config file found.")
else:
data = f.read()
print("Loaded", len(data), "bytes.")
f.close()
Step 2: Add finally
for cleanup that must happen whether the try succeeded or not.
f = None
try:
f = open("config.json", "r", encoding="utf-8")
data = f.read()
except OSError as err:
print("OS error:", err)
finally:
if f:
f.close()
Method 3 — Guard your entry point to fail fast and log
Step 1: Move your workflow into a main()
function.
def main():
# core workflow
# ...
return 0
Step 2: Wrap the call in a top-level try
/except
to log unexpected errors and stop the program.
import logging, sys
logging.basicConfig(level=logging.INFO)
if __name__ == "__main__":
try:
code = main()
except Exception:
logging.exception("Unhandled error")
sys.exit(1)
sys.exit(code)
Step 3: Return a non‑zero exit code on failure to signal automation tools that the run failed.
# in main(), return 1 or raise an exception when a critical step fails
Method 4 — Manage resources with with
(preferred over manual finally)
Step 1: Replace manual open/close with a context manager to enforce timely release.
from pathlib import Path
try:
with Path("data.txt").open("r", encoding="utf-8") as f:
print(f.readline().strip())
except FileNotFoundError:
print("Create data.txt first.")
Step 2: Keep only the risky operations inside the protected block to avoid catching unrelated errors.
try:
with open("image.png", "rb") as fh:
blob = fh.read()
except OSError as e:
print("File access failed:", e)
# decode/process blob in a separate block
Method 5 — Raise, re-raise, and chain exceptions for clear error paths
Step 1: Raise a specific exception when a precondition fails.
def parse_age(s: str) -> int:
if not s.isdigit():
raise ValueError("age must contain only digits")
age = int(s)
if age < 0:
raise ValueError("age cannot be negative")
return age
Step 2: Re-raise unexpected errors after logging so callers can also handle them.
import logging
try:
do_risky_thing()
except OSError as err:
logging.error("OS error: %s", err)
raise
Step 3: Chain exceptions with raise ... from e
to preserve the root cause while adding context.
from pathlib import Path
import json
def load_json(path: str):
try:
text = Path(path).read_text(encoding="utf-8")
except OSError as e:
raise RuntimeError(f"Failed to read {path}") from e
return json.loads(text)
Method 6 — Catch-all handling at boundaries (use sparingly)
Step 1: Use except Exception as e
to capture any non–system-exiting error, log details, and decide whether to stop or retry.
import traceback
try:
risky()
except Exception as e:
print(f"Unexpected {type(e).__name__}: {e}")
traceback.print_exc() # full stack trace
# decide: raise, return error, or abort
Step 2: Only catch BaseException
if you intentionally intercept KeyboardInterrupt
or SystemExit
, and then re-raise immediately.
try:
service_loop()
except BaseException as e:
print(f"Caught {type(e).__name__}; shutting down cleanly.")
raise # never swallow interrupts or SystemExit
Method 7 — Handle multiple failures with ExceptionGroup and except* (Python 3.11+)
Step 1: Raise an ExceptionGroup
when aggregating errors from parallel or batched work.
def run_tests(tests):
errors = []
for t in tests:
try:
t.run()
except Exception as e:
e.add_note(f"Test {t.name} failed")
errors.append(e)
if errors:
raise ExceptionGroup("Batch failures", errors)
Step 2: Use except*
to handle only matching exception types within the group.
try:
run_tests(tests)
except* (ValueError, TypeError):
print("Some data errors occurred.")
except* OSError:
print("Some OS errors occurred.")
Quick troubleshooting patterns
- Input parsing: wrap
int()
orfloat()
withexcept ValueError
and reprompt the user. - File I/O: catch
FileNotFoundError
,PermissionError
, or genericOSError
, and switch to a fallback path. - Networking/API calls: catch library-specific timeouts/connection errors and implement a bounded retry with backoff.
- Always log the exception type and message; for full traces, use
logging.exception()
ortraceback.print_exc()
.
A few focused patterns like specific except
clauses, else
/finally
, and top-level guards will make your Python programs more resilient without hiding real bugs.
Comments