Skip to content

Catch Exceptions with Try Except in Python

Shivam Malani
Catch Exceptions with Try Except in Python

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 and finally 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 or except BaseException) for top-level boundaries where you can fail fast or report clearly.

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() or float() with except ValueError and reprompt the user.
  • File I/O: catch FileNotFoundError, PermissionError, or generic OSError, 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() or traceback.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.