Python for AIPython 3.12 · Pydantic 2.12
Dashboard
0%
1
Curious builder0 XP earned · 300 to level 2
0 daysFinish a lesson to begin
Badge collection0 of 6 unlocked
43 small wins to finish your pathNext lesson →

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:

python
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:

Example
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))

check_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.

python
class TriageError(Exception):
    pass                       # nothing to add; it is an Exception with a new name
Example
def 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)

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:

python
class TriageError(Exception):
    def __init__(self, ticket_id, answer):
        super().__init__(f"ticket {ticket_id}: could not sort {answer!r}")
        self.ticket_id = ticket_id
Example
try:
    raise TriageError(4, "I am not sure")
except TriageError as error:
    print(error)
    print("needs a person:", error.ticket_id)

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

Example
try:
    raise TriageError("bad answer")
except Exception as error:
    print(type(error).__name__, "is an Exception:", isinstance(error, Exception))

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)
NameA generic built-inMeans one thing in your code
Caught byexcept ValueErrorexcept TriageError (and except Exception)
Extra dataOnly the messageAny 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.
Watch out. Subclass 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.
Try it yourself
  • Give TriageError a second attribute, customer, and print it in the except block.
  • Make check_priority raise a TriageError instead of a ValueError, and catch it.
  • Define class ParseError(TriageError) and confirm except TriageError still catches a ParseError.

You understood something today that you didn't yesterday.