Python · errors

Custom Exceptions

Define your own exception classes so errors carry a clear name and the data needed to handle them.

A simple custom exception


        # define custom exception
        class CustomError(Exception):
            pass
        # ans: raise it
        raise CustomError("Something went wrong")
      

Inheriting from Exception is all it takes; pass keeps everything else from the parent.

Exception with attributes


        # exception with data
        class ValidationError(Exception):
            def __init__(self, field, message):
                self.field = field
                self.message = message
                super().__init__(f"{field}: {message}")
      

super().__init__(...) sets the text you see when the exception is printed.

Using a custom exception


          # raise custom exception
          try:
              raise ValidationError("email", "Invalid format")
          except ValidationError as e:
              print(e.field)
              print(e.message)
        

Exception hierarchy


        # base exception
        class DatabaseError(Exception):
            pass
        # ans: specific exceptions
        class ConnectionError(DatabaseError):
            pass
        class QueryError(DatabaseError):
            pass
      

One base class lets callers catch every database problem with a single except DatabaseError. (This ConnectionError shadows Python's built-in one of the same name.)

Which except catches it?

raise
except

An except matches its own class and every class below it.

Exercises

Predict, then reveal.

Exercise 1


        # inherit from what?
        class MyError(Exception):
            pass
        # ans: inherits from Exception
      

Exercise 2


        # can inherit from BaseException?
        class MyError(BaseException):
            pass
        # ans: yes, but use Exception instead
      

except Exception would not catch it, just like KeyboardInterrupt.

Exercise 3


        # custom message?
        class MyError(Exception):
            pass
        raise MyError("Custom message")
        # ans: works!
      

Exercise 4


        # catch custom exception?
        try:
            raise CustomError()
        except CustomError:
            print("Caught")
        # ans: "Caught"
      

Exercise 5


        # exception with default message?
        class MyError(Exception):
            def __init__(self):
                super().__init__("Default message")
      

raise MyError() now shows MyError: Default message.

Exercise 6


        # multiple custom exceptions?
        class Error1(Exception):
            pass
        class Error2(Exception):
            pass
        try:
            pass
        except (Error1, Error2):
            pass
      

A tuple in except catches any of the listed classes.

Exercise 7


        # exception hierarchy catch?
        class Base(Exception):
            pass
        class Derived(Base):
            pass
        try:
            raise Derived()
        except Base:
            print("Caught")
        # ans: "Caught"
      

A Derived is a kind of Base, so except Base matches it.

Exercise 8


        # custom exception attributes?
        class MyError(Exception):
            def __init__(self, code):
                self.code = code
      

Handlers can read e.code. Without super().__init__, the printed message is just the code.

Exercise 9


        # exception with __str__?
        class MyError(Exception):
            def __str__(self):
                return "Custom string"
      

str(e) and the traceback now show Custom string.

Exercise 10


        # empty custom exception?
        class MyError(Exception):
            pass
        # ans: valid minimal exception
      

Remember

Inherit from Exception

Not BaseException; a bare pass body is enough.

Carry data

Store fields in __init__ and pass a message to super().__init__.

Build a family

A base class per area lets callers catch broadly or narrowly.

Matching walks up

except Parent catches every child class too.