Custom exceptions
A custom exception is a class you define by subclassing Exception, so your code can raise and catch an error that names exactly what went wrong.
Last updated: 13 Oct, 2026 · Python 3.12
In try and except you caught errors Python raised, such as json.JSONDecodeError. Now you raise your own, with a name and a message that fit your program.
Syntax:
raise ValueError("message") # raise a built-in error
class MyError(Exception): # your own error type
pass
raise MyError("message")Raising a built-in error
raise stops the program with an error you choose. A value that is out of range is a good reason:
def check_priority(priority):
if priority < 1 or priority > 5:
raise ValueError(f"priority must be 1 to 5, got {priority}")
return priority
print(check_priority(4))
print(check_priority(9))4
Traceback (most recent call last):
File "main.py", line 7, in <module>
print(check_priority(9))
File "main.py", line 3, in check_priority
raise ValueError(f"priority must be 1 to 5, got {priority}")
ValueError: priority must be 1 to 5, got 9check_priority(4) returns and prints. check_priority(9) reaches raise, which stops the program and prints the error and your message. Delete the last line before adding the next example: an error ends the file where it happens.
Defining your own exception type
ValueError is vague when the problem is specific. A class that subclasses Exception gives the error its own name, so callers can catch this one and let others through.
class TriageError(Exception):
pass # nothing to add; it is an Exception with a new namedef parse(answer):
try:
return json.loads(answer)
except json.JSONDecodeError:
# wrap the low-level json error in our own type so callers catch one thing
raise TriageError(f"model did not return JSON: {answer!r}")
try:
parse("I am not sure how to sort this one.")
except TriageError as error:
print("caught:", error)caught: model did not return JSON: 'I am not sure how to sort this one.'
parse catches the low-level json.JSONDecodeError and raises a TriageError in its place, with a message naming the answer. The caller now catches TriageError, an error that means one thing in this program, instead of a generic JSON error.
Carrying data on the exception
An exception is an object, so it can hold more than a message. Store the ticket id on it and the handler can log which ticket failed:
class TriageError(Exception):
def __init__(self, ticket_id, answer):
super().__init__(f"ticket {ticket_id}: could not sort {answer!r}")
self.ticket_id = ticket_idtry:
raise TriageError(4, "I am not sure")
except TriageError as error:
print(error)
print("needs a person:", error.ticket_id)ticket 4: could not sort 'I am not sure' needs a person: 4
super().__init__(...) passes the message up to Exception, so printing the error still shows text. The extra self.ticket_id is yours to read in the except block.
A custom exception is still an Exception
try:
raise TriageError("bad answer")
except Exception as error:
print(type(error).__name__, "is an Exception:", isinstance(error, Exception))TriageError is an Exception: True
Because TriageError subclasses Exception, a broad except Exception still catches it, and the Inheritance relationship is what makes that work. Catch the specific type when you can handle it, the broad one only to log and re-raise.
Built-in error vs your own exception
raise ValueError(...) | class TriageError(Exception) | |
|---|---|---|
| Name | A generic built-in | Means one thing in your code |
| Caught by | except ValueError | except TriageError (and except Exception) |
| Extra data | Only the message | Any attribute you add, such as the ticket id |
Where custom exceptions show up in AI code
- Frameworks raise their own: Pydantic raises
ValidationError, an HTTP client raises its own request errors, so you can catch exactly that failure. - Turning a library's low-level error into one that names your step, such as a model answer that could not be parsed.
- Carrying the id of the record that failed, so a long run can log it and carry on.
Exception, not BaseException. BaseException also covers KeyboardInterrupt and the signal to exit, and a handler that catches those stops the reader from quitting the program with Ctrl+C.Related
- Previous: try and except
- Next: Classes
- Reference: User-defined Exceptions in the Python tutorial
- Give
TriageErrora second attribute,customer, and print it in theexceptblock. - Make
check_priorityraise aTriageErrorinstead of aValueError, and catch it. - Define
class ParseError(TriageError)and confirmexcept TriageErrorstill catches aParseError.
You understood something today that you didn't yesterday.