Interview frequency: …
Short answer
When the caller needs to distinguish a specific failure in the domain and make a different decision based on it, or when you need to attach structured data to the error.
Explanation
Create a base exception for your library or module (such as PaymentError inheriting Exception), then subtypes for cases that are handled differently (such as CardDeclined). This lets the caller catch every error from the library, or only one specific case. Make the name express the cause and add useful fields as attributes. Do not create a class for every text message; the real value lies in being catchable and in the accompanying data.
Code example
class PaymentError(Exception):
"""Base error for the payments module."""
class CardDeclined(PaymentError):
def __init__(self, reason: str, retryable: bool = False):
super().__init__(f"card declined: {reason}")
self.reason = reason
self.retryable = retryable
try:
charge(order)
except CardDeclined as err:
if err.retryable:
schedule_retry(order)Common mistakes
- Inheriting from BaseException instead of Exception.
- Creating dozens of exceptions that nobody catches separately.
Follow-up questions
- How do you choose the right exception and deal with the exception hierarchy?
- What is raise ... from for?