Exceptions
What exceptions are
Section titled “What exceptions are”An exception is a signal that something went wrong during execution.
When Python raises an exception, it unwinds the call stack until it finds a matching except clause or terminates the program.
Exceptions are objects — instances of classes that inherit from BaseException. The ones you will work with in normal code almost always inherit from Exception.
The full try statement
Section titled “The full try statement”Python’s try statement has four optional clauses:
try: result = risky_operation()except SomeError as e: # runs only if SomeError (or a subclass) was raised handle(e)else: # runs only if NO exception was raised in try use(result)finally: # ALWAYS runs — exception or not cleanup()The else clause is a useful Python feature: it lets you distinguish “the operation succeeded” from “we are in cleanup.” Code in else only runs on the happy path.
Catching specific exception types
Section titled “Catching specific exception types”Always catch the most specific exception type you can. Catching Exception is a last resort; catching bare except: silently swallows KeyboardInterrupt and SystemExit as well.
try: n = int(user_input)except ValueError: print("Not a valid integer")except TypeError: print("Expected a string, not None")To catch multiple types in one clause, use a tuple:
except (ValueError, TypeError) as e: print(f"Input error: {e}")Raising exceptions
Section titled “Raising exceptions”Use raise to signal an error explicitly:
def divide(a: float, b: float) -> float: if b == 0: raise ValueError("divisor cannot be zero") return a / bRe-raise the current exception inside an except block with a bare raise:
except ValueError: log_error() raise # re-raises the same exception unchangedException chaining with raise ... from
Section titled “Exception chaining with raise ... from”When you catch one exception and raise another, use raise NewError(...) from original to preserve the original as the cause. This keeps the full traceback in the error chain.
try: value = int(raw_string)except ValueError as original: raise RuntimeError("Failed to parse config") from originalThe __cause__ attribute on the new exception holds the original. Python displays both tracebacks automatically.
To explicitly discard the cause (suppress the chained traceback), use raise ... from None.
Full runnable demo
Section titled “Full runnable demo”def safe_divide(a: float, b: float) -> float: """Divide a by b, raising ValueError if b is zero.""" if b == 0: raise ValueError("divisor cannot be zero") return a / b
# --- try / except / else / finally ---print("=== basic try/except/else/finally ===")for b in [2.0, 0]: try: result = safe_divide(10.0, b) except ValueError as e: print(f" ValueError: {e}") else: print(f" Result: {result}") finally: print(" finally ran")
# --- catching specific types ---print("\n=== specific exception types ===")for val in ["42", "abc", None]: try: n = int(val) print(f" parsed {val!r} -> {n}") except (TypeError, ValueError) as e: print(f" {type(e).__name__} for {val!r}: {e}")
# --- exception chaining ---print("\n=== exception chaining ===")try: try: raw = "not-a-number" value = int(raw) except ValueError as original: raise RuntimeError("Failed to parse config value") from originalexcept RuntimeError as e: print(f" RuntimeError: {e}") print(f" __cause__: {e.__cause__}")Loading Python runtime (first run only)…