Technology  /  Python

🐍 Python 78 guides · updated 2026

From first variable to OOP, generators, and real projects β€” the language that runs everything from data pipelines to AI agents, taught the practical way.

Handling Multiple Exceptions in Python: Grouping, Ordering, and Exception Groups

Most real functions can fail in more than one way. A file-loading function might encounter a missing file, permission denied, or invalid content. Handling multiple exception types correctly means knowing how to write distinct handlers for each, how to group related exceptions, and how to order them so the right handler fires.

Separate except Blocks for Different Failures

The most explicit approach: one except block per exception type, each with its own response.

def load_user_data(path):
try:
with open(path) as f:
import json
data = json.load(f)
return data
except FileNotFoundError:
print(f"File not found: {path}")
return None
except PermissionError:
print(f"Permission denied reading: {path}")
return None
except json.JSONDecodeError as e:
print(f"Invalid JSON in {path} at line {e.lineno}: {e.msg}")
return None
except OSError as e:
print(f"OS error reading {path}: {e}")
return None
result = load_user_data("users.json")

Each failure mode has a specific, meaningful message. Callers who call load_user_data get a None in every failure case, but the printed message tells them why.

Grouping Exceptions in a Tuple

When two or more exception types should receive identical handling, group them in a single except clause using a tuple.

def parse_input(value):
try:
return int(value)
except (ValueError, TypeError) as e:
print(f"Cannot convert {value!r} to int: {e}")
return 0
print(parse_input("42")) # 42
print(parse_input("hello")) # Cannot convert 'hello' to int: ...
print(parse_input(None)) # Cannot convert None to int: ...

ValueError handles the case where the string cannot be converted (int("hello")). TypeError handles passing something that isn’t a string or number at all (int(None)). Since both lead to the same fallback (return 0), grouping them is clean.

Ordering Matters: Specific Before General

Python’s exception hierarchy is a class tree. If you list a parent exception before its child, the parent’s handler will always fire and the child’s handler will never be reached.

class AppError(Exception):
pass
class DatabaseError(AppError):
pass
class ConnectionError(DatabaseError):
pass
# Wrong order β€” DatabaseError catches ConnectionError first
try:
raise ConnectionError("host unreachable")
except DatabaseError:
print("Database error") # fires β€” ConnectionError IS-A DatabaseError
except ConnectionError:
print("Connection error") # unreachable
# Correct order β€” most specific first
try:
raise ConnectionError("host unreachable")
except ConnectionError:
print("Connection error") # fires correctly
except DatabaseError:
print("Database error") # fallback for other DB errors
except AppError:
print("App error") # fallback for everything else

Rule: more specific exceptions come before less specific ones. When in doubt, check the inheritance chain.

Using Exception as a Catch-All

At the top level of a program β€” like a web request handler or a job runner β€” catching Exception broadly can prevent one failing job from crashing the whole process.

def run_job(job_id, func, *args):
try:
result = func(*args)
print(f"Job {job_id} succeeded: {result}")
return result
except Exception as e:
# Log the full traceback, return a failure signal
import traceback
print(f"Job {job_id} failed: {type(e).__name__}: {e}")
traceback.print_exc()
return None

Even here, be precise about what you do with the error. Logging the traceback and continuing is fine. Silently ignoring it (except Exception: pass) is not.

Do not catch BaseException β€” it includes KeyboardInterrupt and SystemExit, which should propagate to let the program exit cleanly.

Re-raising After Catching

Sometimes you want to log or annotate an exception and then let it continue propagating.

import logging
def process_payment(amount):
try:
charge_card(amount)
except PaymentGatewayError as e:
logging.error(f"Payment failed for Β£{amount}: {e}")
raise # re-raises the same exception with the same traceback

Plain raise (with no arguments) re-raises the current exception. The traceback points to the original source, not to this function.

Python 3.11+: ExceptionGroup for Concurrent Errors

Python 3.11 introduced ExceptionGroup for situations where multiple exceptions occur simultaneously β€” common in async code and task runners.

# Python 3.11+
def run_batch(tasks):
errors = []
results = []
for task in tasks:
try:
results.append(task())
except Exception as e:
errors.append(e)
if errors:
raise ExceptionGroup("batch processing failed", errors)
return results
try:
run_batch([...])
except* ValueError as eg:
print(f"Value errors: {eg.exceptions}")
except* IOError as eg:
print(f"IO errors: {eg.exceptions}")

The except* syntax handles each exception type within the group independently. An ExceptionGroup can be partially handled β€” some sub-exceptions are caught while others propagate.

Practical Example: Multiple Failure Modes

import csv
import os
def import_csv(path, required_columns):
"""Load a CSV and validate it has the required columns."""
if not os.path.exists(path):
raise FileNotFoundError(f"No file at {path}")
try:
with open(path, newline="") as f:
reader = csv.DictReader(f)
rows = list(reader)
except PermissionError:
raise PermissionError(f"Cannot read {path} β€” check permissions") from None
except csv.Error as e:
raise ValueError(f"Malformed CSV in {path}: {e}") from e
if not rows:
raise ValueError(f"CSV file {path} is empty")
missing = required_columns - set(rows[0].keys())
if missing:
raise ValueError(f"CSV missing required columns: {missing}")
return rows
# Caller handles each failure type appropriately
try:
data = import_csv("contacts.csv", {"name", "email", "phone"})
except FileNotFoundError as e:
print(f"Upload failed β€” file missing: {e}")
except PermissionError as e:
print(f"Upload failed β€” access denied: {e}")
except ValueError as e:
print(f"Upload failed β€” bad data: {e}")

Common Mistakes

Listing a general exception before a specific one. The more specific one will never match.

Grouping unrelated exceptions that need different handling. Grouping ValueError and ConnectionError in one block means you cannot give them different responses.

Forgetting that except matches subclasses. Catching Exception catches everything that inherits from it β€” which is almost all exceptions. Catching OSError catches FileNotFoundError, PermissionError, and others.

Using multiple bare except: clauses. Python allows only one bare except: per try block, and it must come last. Multiple specific except ExceptionType: clauses are fine.