flowchart TD
A["inside except ZeroDivisionError"] --> B{"how do you raise?"}
B -->|"raise ValueError(...) from e"| C["__cause__ = the original"]
C --> D["traceback: 'The above exception was the direct cause'"]
B -->|"raise ValueError(...)"| E["__cause__ = None, __context__ = the original"]
E --> F["traceback: 'During handling ... another exception occurred'"]
B -->|"raise ValueError(...) from None"| G["__cause__ = None, display suppressed"]
G --> H["the original is hidden from the traceback"]
H --> I["__context__ is STILL set -- hidden, not erased"]
Why raise exceptions?
You can use raise to:
- validate function inputs
- enforce rules
- stop invalid workflows early
def set_age(age: int) -> int:
if age < 0:
raise ValueError("age must be >= 0")
return age
print(set_age(10))
# print(set_age(-1))Raise with a custom message
score = -5
if score < 0:
raise ValueError(f"score must be non-negative, got {score}")Creating custom exceptions
Custom exceptions help you represent domain-specific errors.
class PaymentError(Exception):
"""Raised when a payment fails."""
def charge(amount: float) -> None:
if amount <= 0:
raise PaymentError("Amount must be positive")
try:
charge(0)
except PaymentError as e:
print("Payment failed:", e)Re-raising exceptions
Sometimes you want to log something and re-raise.
try:
int("abc")
except ValueError as e:
print("Logging error:", e)
raiseBest practices
- Don’t leak secrets in error messages.
- Use clear messages: what went wrong + what to do.
- Prefer built-in exception types when they fit.
Check yourself
Inside an `except`, you write `raise ValueError(...) from e`. What is set?
Verified. `from e` sets `__cause__` explicitly, and `__context__` is set automatically because you were handling something at the time.
Show answer
C — Both `__cause__` and `__context__` — Verified. `from e` sets `__cause__` explicitly, and `__context__` is set automatically because you were handling something at the time.
What does `raise ... from None` actually do?
Verified: `__cause__` is None but `__context__` still holds the original. Anything walking the chain programmatically can still reach it.
Show answer
B — Suppresses its DISPLAY; `__context__` is still set — Verified: `__cause__` is None but `__context__` still holds the original. Anything walking the chain programmatically can still reach it.
A traceback says 'During handling of the above exception, another exception occurred'. What does that indicate?
That wording comes from `__context__`. 'The above exception was the direct cause' is the other one, produced by `raise ... from`.
Show answer
B — Implicit chaining — a bare `raise` inside an `except` — That wording comes from `__context__`. 'The above exception was the direct cause' is the other one, produced by `raise ... from`.
When should you wrap a library exception in your own type with `from e`?
Wrapping gives callers a stable exception type to catch, and `from e` keeps the root cause in the traceback so debugging is not made harder.
Show answer
B — When the original explains the new one and callers should not depend on the library's type — Wrapping gives callers a stable exception type to catch, and `from e` keeps the root cause in the traceback so debugging is not made harder.
🧪 Try It Yourself
Exercise 1 – Raise a Built-in Exception
Tab indents. Press Escape, then Tab, to leave the editor. Control plus Enter runs the code; Control plus Shift plus Enter submits it.
Press Run to see what your code prints.Exercise 2 – Define a Custom Exception
Tab indents. Press Escape, then Tab, to leave the editor. Control plus Enter runs the code; Control plus Shift plus Enter submits it.
Press Run to see what your code prints.Exercise 3 – Re-raise an Exception
Tab indents. Press Escape, then Tab, to leave the editor. Control plus Enter runs the code; Control plus Shift plus Enter submits it.
Press Run to see what your code prints.