Python How to define a custom exception with parameters

eye-catch Python

An application needs to behave differently depending on an exception. If we need to handle an app-specific or business-specific error, we need to define our own exception.

If not using a custom exception, the code will look like the following.

try:
    raise  Exception("Database is not ready")
except Exception as err:
    if "Database is not ready" in str(err):
        # Do something special here
        print("wait for the initilization of the Database")
    else:
        print("unknown error")

This is not a good design. The message might change in the future and it makes a bug. Let’s learn how to create a custom exception.

If you want to have the complete code, you can check it in my GitHub repository.

Sponsored links

Define a custom exception that can take an error message

The easiest way to define a custom exception is the following.

class CustomException(Exception):
    pass

It just extends the Exception and gives a different name to it. The previous code can be written in the following way.

try:
    raise CustomException("Database is not ready")
except CustomException as err:
    print(f"error: {str(err)}")
    print("wait for the initilization of the Database")
except Exception as err:
    print("unknown error")

# error: Database is not ready
# wait for the initilization of the Database

An except keyword is added but if-else is removed. Even if the error message changes, the error handling still works.

If the thrown error is not CustomException, it will be handled in Exception error handling.

Sponsored links

Fixed error message

If we want the same error message for all the places where the exception is used, specify the error message in the constructor.

class MyExceptionWithFixedMsg(Exception):
    def __init__(self):
        super().__init__("An error occurred for some reason")

try:
    raise MyExceptionWithFixedMsg()
except MyExceptionWithFixedMsg as err:
    print(str(err))

# An error occurred for some reason

When the error is thrown, the error message is always this one.

Specify some parameters

If some parameters are needed, add them to __init__ function and assign them to self.variable_name.

class MyExceptionWithParameters(Exception):
    def __init__(self, error_code):
        self.error_code = error_code
        super().__init__(f"MyExceptionWithParameters error message. error_code: {error_code}")

try:
    raise MyExceptionWithParameters(123)
except MyExceptionWithParameters as err:
    print(f"error_code: {err.error_code}")
    print(f"str(err): {str(err)}")

# error_code: 123
# str(err): MyExceptionWithParameters error message. error_code: 123

Extend a custom exception

A custom exception can be extended for example when you want to add parameters to the base class.

class MyExtendedException(MyExceptionWithFixedMsg):
    def __init__(self, error_code):
        self.error_code = error_code
        super().__init__()

try:
    raise MyExtendedException(123)
except MyExtendedException as err:
    print(f"error_code: {err.error_code}")
    print(f"str(err): {str(err)}")

# error_code: 123
# str(err): An error occurred for some reason

Note that super().__init__() needs to be called, otherwise, it doesn’t make sense to extend the class. If it doesn’t call it, the result will be like this.

class MyExtendedException(MyExceptionWithFixedMsg):
    def __init__(self, error_code):
        self.error_code = error_code

try:
    raise MyExtendedException(123)
except MyExtendedException as err:
    print(f"error_code: {err.error_code}")
    print(f"str(err): {str(err)}")

# error_code: 123
# str(err): 123

Overwrite the error message

By the way, it can’t pass an error message to the super().__init__() because MyExceptionWithFixedMsg class doesn’t have the parameter. However, there might be some cases where we want to update the error message for the extended class. Define __str__() method in this case.

class MyExtendedException(MyExceptionWithFixedMsg):
    def __init__(self, error_code):
        self.error_code = error_code
        super().__init__()

    def __str__(self):
        return f"MyExtendedException(error_code: {self.error_code})"

try:
    raise MyExtendedException(error_code=123)
except MyExtendedException as err:
    print(f"error_code: {err.error_code}")
    print(f"str(err): {str(err)}")

# error_code: 123
# str(err): MyExtendedException(error_code: 123)

__str__() is called when the object needs to be converted to string. So, the error message can be overwritten.

Named parameters

It might be better to have named parameters to make it readable. In this case, add an asterisk to the parameter. The parameters defined after the asterisk can be specified with the name.

class TooColdException(Exception):
    def __init__(
        self,
        *,
        error_code=None,
        extra_msg=None,
    ):
        self.error_code = error_code
        self.extra_msg = extra_msg

        if error_code == 1:
            action = "Close all windows."
        elif error_code == 2:
            action = "Turn the heater on."
        else:
            action = "Wait for Summer."

        super().__init__(
            f"Too Cold Exception occurred.\n"
            f"Error code: {error_code}\n"
            f"Action: {action}\n"
            f"Extra_msg: {extra_msg}"
        )

try:
    raise TooColdException(error_code=123, extra_msg="hello")
except TooColdException as err:
    print(f"error_code: {err.error_code}, extra_msg: {err.extra_msg}")
    print(f"str(err): {str(err)}")

# error_code: 123, extra_msg: hello
# str(err): Too Cold Exception occurred.
# Error code: 123
# Action: Wait for Summer.
# Extra_msg: hello

Handling error depending on the exception type

If a function can throw many types of exceptions, we need to handle them correctly. Basically, the error should be handled depending on the exception type but if the exception class has an error code, we can also handle them.

errors = [
    MyExceptionWithFixedMsg(),
    MyExtendedException(55),
    Exception("Normal error"),
    TooColdException(),
    TooColdException(error_code=1),
    TooColdException(
        error_code=2, extra_msg="It is 22 degrees in the room though."
    ),
    TooColdException(error_code=3),
]

for error in errors:
    try:
        raise error
    except TooColdException as err:
        print(f"TooColdException: {str(err)}")
        if err.error_code == 1:
            print("-----> I will close all windows later.")
        elif err.error_code == 2:
            print("-----> I will turn the heater on in 30 minutes.")
        else:
            print(
                f"-----> I don't know what to do for error code [{err.error_code}]"
            )

    except MyExtendedException as err:
        print(f"MyExtendedException: {str(err)}, error_code: {err.error_code}")
    except MyExceptionWithFixedMsg as err:
        print(f"MyExceptionWithFixedMsg: {str(err)}")
    except Exception as err:
        print(f"Exception: {str(err)}")
    print()

# MyExceptionWithFixedMsg: An error occurred for some reason

# MyExtendedException: An error occurred for some reason, error_code: 55

# Exception: Normal error

# TooColdException: Too Cold Exception occurred.
# Error code: None
# Action: Wait for Summer.
# Extra_msg: None
# -----> I don't know what to do for error code [None]

# TooColdException: Too Cold Exception occurred.
# Error code: 1
# Action: Close all windows.
# Extra_msg: None
# -----> I will close all windows later.

# TooColdException: Too Cold Exception occurred.
# Error code: 2
# Action: Turn the heater on.
# Extra_msg: It is 22 degrees in the room though.
# -----> I will turn the heater on in 30 minutes.

# TooColdException: Too Cold Exception occurred.
# Error code: 3
# Action: Wait for Summer.
# Extra_msg: None
# -----> I don't know what to do for error code [3]

It works pretty well.

raise an Exception from another exception

An Exception can be thrown from another exception. It can wrap the original exception and generate a new exception.

try:
    try:
        raise MyExceptionWithParameters(999)
    except Exception as err:
        raise TooColdException(
            error_code=12,
            extra_msg="It's cold.",
        ) from err  # raise an exception with from keyword here
except Exception as error:
    print(isinstance(error, Exception))  # True
    print(isinstance(error, MyExceptionWithParameters))  # False
    print(isinstance(error, TooColdException))  # True

Since the exception is wrapped, it is not an instance of the original exception. This from keyword can be useful if the exception is caused by another exception and we want to know the root cause. The wrapped error message can be read via __cause__.

try:
    try:
        raise MyExceptionWithParameters(999)
    except Exception as err:
        raise TooColdException(
            error_code=12,
            extra_msg="It's cold.",
        ) from err
except TooColdException as error:
    print(f"error code: {error.error_code}")
    print(f"extra msg: {error.extra_msg}")
    print(f"str(error) >>> {str(error)}")
    print(f"str(error.__cause__) >>> {str(error.__cause__)}")

# error code: 12
# extra msg: It's cold.
# str(error) >>> Too Cold Exception occurred.
# Error code: 12
# Action: Wait for Summer.
# Extra_msg: It's cold.
# str(error.__cause__) >>> MyExceptionWithParameters error message. error_code: 999

error.__cause__ is None if there is no from keyword there.

Comments

Copied title and URL