raise and Custom Exceptions
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?
Section titled “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
Section titled “Raise with a custom message”score = -5
if score < 0:
raise ValueError(f"score must be non-negative, got {score}")Creating custom exceptions
Section titled “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
Section titled “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
Section titled “Best 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
Section titled “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.
pch.quizShowAnswer
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.
pch.quizShowAnswer
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`.
pch.quizShowAnswer
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.
pch.quizShowAnswer
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
Section titled “🧪 Try It Yourself”Exercise 1 – Raise a Built-in Exception
Section titled “Exercise 1 – Raise a Built-in Exception”Exercise 2 – Define a Custom Exception
Section titled “Exercise 2 – Define a Custom Exception”Exercise 3 – Re-raise an Exception
Section titled “Exercise 3 – Re-raise an Exception”pch.coffeeTagline
pch.coffeeCtapch.feedbackHeading
pch.feedbackSubheading