Skip to content

Exceptions

Quality Score

Overall Score: 8.6/10 ✅ Excellent

  • Technical Accuracy: 28/35
  • Code Quality: 24/25
  • Educational Value: 21/25
  • Documentation: 13/15

Last reviewed: June 25, 2026

Exceptions are a mechanism for handling errors in Python (and most programming languages). When an error occurs when running a piece of code, Python raises an exception. There are multiple types of exceptions (built-in and custom). BaseException is the base class for all built-in exceptions, but commonly used exceptions inherit from Exception. You can also create your own custom exceptions by inheriting from Exception or any of its subclasses.

If the exception is not caught, the program will terminate immediately, raising the corresponding exception and traceback.

src.intermediate.exceptions.exception_uncontrolled()

Example of an unhandled exception that propagates to the caller.

This function intentionally performs an invalid operation (adding an integer and a string) without catching the exception. This shows what happens when exceptions are not handled - they propagate up the call stack until caught or the program crashes.

Raises:

Type Description
TypeError

When attempting to add incompatible types (int + str).

Source code in src/intermediate/exceptions/exceptions.py
15
16
17
18
19
20
21
22
23
24
25
26
27
28
def exception_uncontrolled() -> None:
    """Example of an unhandled exception that propagates to the caller.

    This function intentionally performs an invalid operation (adding an
    integer and a string) without catching the exception. This shows what
    happens when exceptions are not handled - they propagate up the call
    stack until caught or the program crashes.

    Raises:
        TypeError: When attempting to add incompatible types (int + str).
    """
    number = 1
    char = "a"
    number + char  # raise TypeError, cannot sum int&string types

Always raise proper exception when building your python code. It will help you and your team to understand the error and how to fix it. A proper module/library, should implement a consistent and robust mechanism to raise exceptions, including in the docstrings sections of the module.

Controlling exceptions

Exceptions can be caught and handled using a try block. You can catch the exception, do something (like logging, metrics...), and continue running the program.

src.intermediate.exceptions.exception_controlled()

Example of proper exception handling by catching and logging.

This function shows how to catch an exception, log it, and continue execution gracefully. This is appropriate when the error is expected and recoverable, and you want the program to continue running.

Source code in src/intermediate/exceptions/exceptions.py
31
32
33
34
35
36
37
38
39
40
41
42
43
44
def exception_controlled() -> None:
    """Example of proper exception handling by catching and logging.

    This function shows how to catch an exception, log it, and continue
    execution gracefully. This is appropriate when the error is expected
    and recoverable, and you want the program to continue running.
    """
    number = 1
    char = "a"
    try:
        number + char  # raise TypeError, cannot sum int&string types
    except TypeError:
        logger.warning("Cannot sum int + string. Continue.")
        pass

You can catch the exception, logging and raise the same exception to terminate the execution.

src.intermediate.exceptions.exception_controlled_raise_exception()

Example of catching and re-raising the same exception.

This function shows how to catch an exception, log relevant information, and then re-raise the same exception. This pattern is useful when you want to add logging or perform cleanup while still propagating the error to the caller.

Raises:

Type Description
TypeError

When attempting to add incompatible types (int + str).

Source code in src/intermediate/exceptions/exceptions.py
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
def exception_controlled_raise_exception() -> None:
    """Example of catching and re-raising the same exception.

    This function shows how to catch an exception, log relevant information,
    and then re-raise the same exception. This pattern is useful when you
    want to add logging or perform cleanup while still propagating the
    error to the caller.

    Raises:
        TypeError: When attempting to add incompatible types (int + str).
    """
    number = 1
    char = "a"
    try:
        number + char  # raise TypeError, cannot sum int&string types
    except TypeError as exc:
        logger.error("Cannot sum int + string. Raising TypeError.")
        raise exc

Similar way, you can catch the exception, logging and raise another your custom exception (always use from to preserve the original exception context). You can terminate the execution of a running program by raising an exception at any time.

src.intermediate.exceptions.exception_controlled_raise_custom_exception(number=1, char='a')

Example of catching and raising a custom exception.

This function shows how to catch a built-in exception and raise a custom exception instead. This pattern is useful for wrapping low-level exceptions with domain-specific errors that provide better context to callers.

Parameters:

Name Type Description Default
number int

An integer to be added. Default is 1.

1
char str

A string to be added. Default is "a".

'a'

Raises:

Type Description
CustomError

A custom exception wrapping the original TypeError.

Source code in src/intermediate/exceptions/exceptions.py
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
def exception_controlled_raise_custom_exception(
    number: int = 1,
    char: str = "a",
) -> None:
    """Example of catching and raising a custom exception.

    This function shows how to catch a built-in exception and raise a
    custom exception instead. This pattern is useful for wrapping
    low-level exceptions with domain-specific errors that provide
    better context to callers.

    Args:
        number: An integer to be added. Default is 1.
        char: A string to be added. Default is "a".

    Raises:
        CustomError: A custom exception wrapping the original TypeError.
    """
    try:
        number + char  # raise TypeError, cannot sum int&string types
    except TypeError as exc:
        logger.error("Cannot sum int + string. Raising CustomError.")
        raise CustomError(
            message="Controlled TypeError",
            exception=exc,
        ) from exc

With the finally block, you can run code that will always run, regardless if the code in the try block raises an exception. It will be always executed.

src.intermediate.exceptions.exception_with_finally(raise_exception)

Example of using finally clause for cleanup code.

This function shows how the finally block executes regardless of whether an exception is raised or not. The finally block is useful for cleanup operations that must always run, such as closing files or releasing resources.

Parameters:

Name Type Description Default
raise_exception bool

If True, raises a TypeError. If False, executes successfully without raising an exception.

required

Raises:

Type Description
TypeError

When raise_exception is True and attempting to add incompatible types (int + str).

Source code in src/intermediate/exceptions/exceptions.py
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
def exception_with_finally(raise_exception: bool) -> None:
    """Example of using finally clause for cleanup code.

    This function shows how the finally block executes regardless of
    whether an exception is raised or not. The finally block is useful
    for cleanup operations that must always run, such as closing files
    or releasing resources.

    Args:
        raise_exception: If True, raises a TypeError. If False, executes
            successfully without raising an exception.

    Raises:
        TypeError: When raise_exception is True and attempting to add
            incompatible types (int + str).
    """
    number = 1
    char = "a"
    try:
        if raise_exception:
            number + char  # raise TypeError, cannot sum int&string types
        else:
            number + 10
    except TypeError as exc:
        logger.error("Cannot sum int + string. Raising TypeError.")
        raise exc
    finally:
        logger.debug("Finally is executed.")

Adding the else block, you can run code that will only run if the code in the try block does not raise an exception. It will be executed only if the try block succeeds.

src.intermediate.exceptions.exception_with_else(raise_exception)

Example of using else clause for code that runs if no exception occurs.

This function shows how the else block executes only if no exception is raised in the try block. The else block is useful for code that should run only when the try block succeeds, such as processing results or performing follow-up actions.

Parameters:

Name Type Description Default
raise_exception bool

If True, raises a TypeError. If False, executes successfully without raising an exception.

required

Raises:

Type Description
TypeError

When raise_exception is True and attempting to add incompatible types (int + str).

Source code in src/intermediate/exceptions/exceptions.py
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
def exception_with_else(raise_exception: bool) -> None:
    """Example of using else clause for code that runs if no exception occurs.

    This function shows how the else block executes only if no exception is
    raised in the try block. The else block is useful for code that should
    run only when the try block succeeds, such as processing results or
    performing follow-up actions.

    Args:
        raise_exception: If True, raises a TypeError. If False, executes
            successfully without raising an exception.

    Raises:
        TypeError: When raise_exception is True and attempting to add
            incompatible types (int + str).
    """
    number = 1
    char = "a"
    try:
        if raise_exception:
            number + char  # raise TypeError, cannot sum int&string types
        else:
            number + 10
    except TypeError as exc:
        logger.error("Cannot sum int + string. Raising TypeError.")
        raise exc
    else:
        logger.debug("Else is executed.")

Multiple exceptions can be caught in a single except block by specifying a tuple of exception types. This is useful when you want to handle different exceptions in the same way.

src.intermediate.exceptions.multiple_exceptions_controlled(type_error)

Example of multiple exception handling with specific except blocks.

This function shows how to handle multiple exceptions in a single try block by using multiple except clauses. Each except clause can handle a specific type of exception, allowing for more granular error handling.

Parameters:

Name Type Description Default
type_error bool

If True, raises a TypeError. If False, raises a ValueError.

required

Raises:

Type Description
TypeError

When attempting to add incompatible types (int + str).

ValueError

When attempting to convert a non-numeric string to int.

Source code in src/intermediate/exceptions/exceptions.py
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
def multiple_exceptions_controlled(type_error: bool) -> None:
    """Example of multiple exception handling with specific except blocks.

    This function shows how to handle multiple exceptions in a single try block
    by using multiple except clauses. Each except clause can handle a specific
    type of exception, allowing for more granular error handling.

    Args:
        type_error: If True, raises a TypeError. If False, raises a ValueError.

    Raises:
        TypeError: When attempting to add incompatible types (int + str).
        ValueError: When attempting to convert a non-numeric string to int.
    """
    number = 1
    char = "a"
    try:
        if type_error:
            number + char  # raise TypeError, cannot sum int&string types
        else:
            int(char)  # raise ValueError, cannot convert str to int
    except (TypeError, ValueError) as exc:
        logger.error("An error occurred: %s. Raising the exception.", exc)
        raise exc

You can also create your own custom exceptions, by inheriting from Exception or any of its subclasses. This allows you to create exceptions that are specific to your application or library. You can also add custom attributes and methods to your custom exceptions, enriching the information that can be provided when the exception is raised.

src.intermediate.exceptions.CustomError

Bases: Exception

Custom exception class for demonstrating exception handling patterns.

This class extends the built-in Exception class to create a custom exception that can carry additional context about the error, including the original exception and a custom message.

Attributes:

Name Type Description
message str

A descriptive message explaining the error context.

exception Exception | None

The original exception that triggered this custom error.

Source code in src/intermediate/exceptions/custom_exceptions.py
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
class CustomError(Exception):
    """Custom exception class for demonstrating exception handling patterns.

    This class extends the built-in Exception class to create a custom
    exception that can carry additional context about the error, including
    the original exception and a custom message.

    Attributes:
        message: A descriptive message explaining the error context.
        exception: The original exception that triggered this custom error.
    """

    message: str
    exception: Exception | None

    def __init__(
        self,
        message: str,
        exception: Exception | None = None,
    ) -> None:
        """Initialize CustomError with message and exception.

        Args:
            message (str): A descriptive message explaining the error.
            exception (Exception | None): The original exception being wrapped.
                Defaults to None if no exception is being wrapped.
        """
        super().__init__(message)
        self.message = message
        self.exception = exception

    def __str__(self) -> str:
        """Return a string representation of the CustomError."""
        if self.exception:
            return (
                f"{self.message} (caused by "
                f"{type(self.exception).__name__}: {self.exception})"
            )
        return self.message

Common pitfalls

Do not catch Exception or BaseException unless you have a very good reason and you have great observability of your code. Catching these exceptions can hide bugs and make it difficult to debug your code. The best approach is to catch the exceptions that you expect to occur and handle them appropriately, and let the unexpected exceptions propagate all the way up.

References