When writing Python programs, errors are inevitable. Whether you’re reading a file, parsing user input, or making network requests, things can (and will) go wrong at runtime. If not handled properly, these failures can crash your application and frustrate users.

That’s where exception handling in Python comes in. By mastering try/except, else, finally, and context managers (with), you can write resilient code that handles errors gracefully while still surfacing real bugs when they occur.

In this guide, we’ll walk through the best practices for handling exceptions in Python, complete with code examples, so you can keep your programs reliable, readable, and production-ready.

Core Principles of Exception Handling in Python

Before diving into methods, keep these golden rules in mind:

  • Catch only specific exceptions you expect (ValueError, FileNotFoundError) instead of using blanket handlers.
  • Use else for success paths and finally for guaranteed cleanup.
  • Favor context managers (with) for files and resources.
  • Never silently swallow bugs — log and re-raise them instead.
  • Reserve catch-all handlers for top-level boundaries where you need to log or fail fast.

Method 1 — Catch Specific Exceptions (Recommended)

Instead of handling every possible error, catch the exact ones you expect:

raw = input("Select a fruit number (0-2): ")

try:
    selection = ["apple", "pear", "banana"][int(raw)]
    print("You chose:", selection)
except ValueError:
    print("Not a number.")
except IndexError:
    print("Choice out of range.")

You can also group exceptions when recovery is the same:

try:
    selection = ["apple", "pear", "banana"][int(raw)]
except (ValueError, IndexError) as e:
    print("Invalid selection:", type(e).__name__)

Common exceptions to know:

  • OSError family → File handling
  • ValueError / TypeError → Data issues
  • ZeroDivisionError → Math errors
  • KeyError / IndexError → Collections

Method 2 — Use else and finally for Clean Separation

else runs only if no error occurs, while finally always runs for cleanup.

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()

With finally:

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 the Program Entry Point

Wrap your main workflow with a top-level exception handler to log and fail fast.

import logging, sys

logging.basicConfig(level=logging.INFO)

def main():
    # core workflow
    return 0

if __name__ == "__main__":
    try:
        code = main()
    except Exception:
        logging.exception("Unhandled error")
        sys.exit(1)
    sys.exit(code)

Returning a non-zero exit code signals automation tools that the run failed.

Method 4 — Use Context Managers (with) for Resources

Context managers automatically clean up files, sockets, and other resources.

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.")

Keep only risky operations inside the try block:

try:
    with open("image.png", "rb") as fh:
        blob = fh.read()
except OSError as e:
    print("File access failed:", e)

# process blob outside the block

Method 5 — Raise, Re-Raise, and Chain Exceptions

Raise when conditions fail:

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

Re-raise after logging:

import logging

try:
    do_risky_thing()
except OSError as err:
    logging.error("OS error: %s", err)
    raise

Chain exceptions with 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 (Use Sparingly)

Sometimes you need a broad safety net:

import traceback

try:
    risky()
except Exception as e:
    print(f"Unexpected {type(e).__name__}: {e}")
    traceback.print_exc()

Avoid catching BaseException unless you’re handling shutdown signals:

try:
    service_loop()
except BaseException as e:
    print(f"Caught {type(e).__name__}; shutting down cleanly.")
    raise

Method 7 — Handle Multiple Errors with ExceptionGroup (Python 3.11+)

Aggregate multiple failures with ExceptionGroup:

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)

Handle them selectively with except*:

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
  • File I/O → catch FileNotFoundError, PermissionError, or OSError
  • Networking/API calls → handle timeouts, retries, and connection errors
  • Always log exceptions → use logging.exception() or traceback.print_exc()

Conclusion

Exception handling in Python isn’t just about preventing crashes — it’s about writing clean, maintainable, and fault-tolerant code. By catching specific errors, using else and finally wisely, leveraging context managers, and applying catch-all strategies only when appropriate, you can create applications that are both robust and easy to debug.

Master these techniques, and you’ll be equipped to handle anything Python throws your way — from simple input mistakes to complex multi-error workflows.

G
Enjoy Tech2Geek?

Add Tech2Geek as a preferred source on Google to see more of our latest tech articles.

Add Tech2Geek
☕

Support Tech2Geek ❤️

Did this guide help you? If Tech2Geek saves you time or helps you solve a problem, consider buying me a coffee. ☕
Your support helps keep Tech2Geek independent and free.

☕ Buy Me a Coffee
Every coffee makes a difference. Thank you! 🙏

Categorized in: