PY

Error Handling

Python · 8 entries

try / except

syntax
try:
    ...
except ExceptionType as e:
    ...
example
try:
    value = int("not_a_number")
except ValueError as e:
    print(f"Conversion failed: {e}")

# Multiple exception types
try:
    result = {"a": 1}["b"]
except (KeyError, IndexError) as e:
    print(f"Lookup error: {e}")
output
Conversion failed: invalid literal for int() with base 10: 'not_a_number'
Lookup error: 'b'

Note Always catch specific exceptions. Bare 'except:' catches everything including SystemExit and KeyboardInterrupt, which is almost never what you want.

else & finally

syntax
try:
    ...
except:
    ...
else:
    ...
finally:
    ...
example
def safe_divide(a: float, b: float) -> float | None:
    try:
        result = a / b
    except ZeroDivisionError:
        print("Cannot divide by zero")
        return None
    else:
        print(f"Result: {result}")
        return result
    finally:
        print("Division attempted")

safe_divide(10, 3)
safe_divide(10, 0)
output
Result: 3.3333333333333335
Division attempted
Cannot divide by zero
Division attempted

Note else runs only when no exception occurred. finally always runs, even after return statements. Use else to keep the try block minimal.

Raising Exceptions

syntax
raise ExceptionType(message)
example
def withdraw(balance: float, amount: float) -> float:
    if amount <= 0:
        raise ValueError(f"Amount must be positive, got {amount}")
    if amount > balance:
        raise RuntimeError("Insufficient funds")
    return balance - amount

try:
    withdraw(100, 200)
except RuntimeError as e:
    print(e)
output
Insufficient funds

Note Use raise without arguments inside an except block to re-raise the current exception. Use 'raise NewError() from original' to chain exceptions.

Custom Exception Classes

syntax
class MyError(Exception):
    ...
example
class PaymentError(Exception):
    def __init__(self, amount: float, reason: str):
        self.amount = amount
        self.reason = reason
        super().__init__(f"Payment of ${amount:.2f} failed: {reason}")

try:
    raise PaymentError(49.99, "card declined")
except PaymentError as e:
    print(e)
    print(f"Amount: ${e.amount}")
output
Payment of $49.99 failed: card declined
Amount: $49.99

Note Custom exceptions should inherit from Exception (not BaseException). Add meaningful attributes so callers can inspect the error programmatically.

Exception Groups (3.11+)

syntax
raise ExceptionGroup(msg, [exc1, exc2])
except* ErrorType as eg:
example
def validate(data: dict) -> None:
    errors = []
    if not data.get("name"):
        errors.append(ValueError("name is required"))
    if not data.get("email"):
        errors.append(ValueError("email is required"))
    if errors:
        raise ExceptionGroup("Validation failed", errors)

try:
    validate({})
except* ValueError as eg:
    for err in eg.exceptions:
        print(f"  - {err}")
output
  - name is required
  - email is required

Note except* catches matching exceptions from the group while letting others propagate. ExceptionGroup is ideal for concurrent/batch operations that produce multiple errors.

Exception Chaining (from)

syntax
raise NewError() from original_error
example
class DatabaseError(Exception):
    pass

try:
    int("bad")
except ValueError as e:
    raise DatabaseError("Failed to parse DB record") from e
output
# Traceback shows both: ValueError → DatabaseError

Note Using 'from e' sets __cause__ so the traceback shows the chain. Use 'from None' to explicitly suppress the original exception context.

Common Built-in Exceptions

syntax
ValueError, TypeError, KeyError, IndexError, ...
example
# ValueError - wrong value for the type
# TypeError - wrong type entirely
# KeyError - missing dictionary key
# IndexError - list index out of range
# AttributeError - missing attribute
# FileNotFoundError - file doesn't exist
# PermissionError - insufficient permissions
# StopIteration - iterator exhausted

try:
    open("nonexistent.txt")
except FileNotFoundError:
    print("File not found")
output
File not found

Note Learn the hierarchy: FileNotFoundError is a subclass of OSError. Catching OSError also catches FileNotFoundError, PermissionError, etc.

Suppressing Exceptions

syntax
from contextlib import suppress
with suppress(ExceptionType):
    ...
example
from contextlib import suppress
import os

# Instead of try/except/pass
with suppress(FileNotFoundError):
    os.remove("temp_cache.dat")

print("Cleanup done")
output
Cleanup done

Note contextlib.suppress is cleaner than try/except/pass when you intentionally want to ignore specific exceptions. Do not suppress broad exception types.