Raising an exception in Python is as simple as using the raise keyword. The raise statement takes an exception object, either built-in or user-defined (and, in exceptional situations, objects of other types), and immediately stops normal execution at that point.
| | >>> raise ZeroDivisionError |
| | Traceback (most recent call last): |
| | File "<python-input-0>", line 1, in <module> |
| | raise ZeroDivisionError |
| | ZeroDivisionError |
| | >>> raise Exception |
| | Traceback (most recent call last): |
| | File "<python-input-1>", line 1, in <module> |
| | raise Exception |
| | Exception |
| | >>> raise Exception() |
| | Traceback (most recent call last): |
| | File "<python-input-2>", line 1, in <module> |
| | raise Exception() |
| | Exception |
You should raise exceptions in a function as soon as an error becomes detectable. For example, the function below multiplies a list by a given number of copies. Before doing so, it checks whether the number is positive. If it isn’t, the function raises a ValueError.
Without this check, the function would silently return an empty list, which is likely not what the caller intended:
| | >>> def multiply_list(l: list, n: int) -> list: |
| | ... """Return list `l` replicated `n` times.""" |
| | ... if n <= 0: |
| | ... raise ValueError |
| | ... return n * l |
| | ... |
| | >>> multiply_list([1, 2, 3], 0) |
| | Traceback (most recent call last): |
| | File "<python-input-12>", line 1, in <module> |
| | multiply_list([1, 2, 3], 0) |
| | ~~~~~~~~~~~~~^^^^^^^^^^^ |
| | File "<python-input-11>", line 3, in multiply_list |
| | raise ValueError |
| | ValueError |
Here, ValueError is the appropriate exception to raise, not TypeError. We assume that n has the correct type (int), but its value is invalid. If the type itself is incorrect, Python’s multiplication operator will raise a TypeError on its own.
You can also pass arguments to an exception when you raise it. These arguments are stored in the exception object’s args attribute (see ) and provide additional context during debugging:
| | >>> def multiply_list(l: list, n: int) -> list: |
| | ... """Return list `l` replicated `n` times.""" |
| | ... if n <= 0: |
| | ... raise ValueError(n) |
| | ... return n * l |
| | ... |
| | >>> multiply_list([1, 2, 3], -10) |
| | Traceback (most recent call last): |
| | File "<python-input-26>", line 1, in <module> |
| | multiply_list([1, 2, 3], -10) |
| | ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^ |
| | File "<python-input-25>", line 3, in multiply_list |
| | raise ValueError(n) |
| | ValueError: -10 |
Beyond simple error reporting, raise can also be used for exception chaining: a mechanism that lets you preserve the original cause of an error while raising a new, more abstract, higher-level one. This is especially useful when translating low-level exceptions into higher-level errors without losing valuable debugging information.
Consider the following example. The function load_data tries to open a file and anticipates a FileNotFoundError. Instead of handling that exception directly, it raises a new exception, IOError, with an additional message argument, chaining the new exception to the original:
| | >>> def load_data(filename: str) -> str: |
| | ... """Return the contents of the file `filename`.""" |
| | ... try: |
| | ... with open(filename) as infile: |
| | ... return infile.read() |
| | ... except FileNotFoundError as eo: |
| | ... raise IOError(f'Could not load data from {filename}') from eo |
| | ... |
| | >>> import traceback |
| | ... try: |
| | ... load_data('missing.txt') |
| | ... except IOError as eo: |
| | ... print(eo) |
| | ... print(f'Original cause: {eo.__cause__}') |
| | ... traceback.print_exception(eo) |
| | ... |
| | Could not load data from missing.txt |
| | Original cause: [Errno 2] No such file or directory: 'missing.txt' |
| | Traceback (most recent call last): |
| | File "<python-input-2>", line 3, in load_data |
| | with open(filename) as infile: |
| | ~~~~^^^^^^^^^^ |
| | FileNotFoundError: [Errno 2] No such file or directory: 'missing.txt' |
| | |
| | The above exception was the direct cause of the following exception: |
| | |
| | Traceback (most recent call last): |
| | File "<python-input-5>", line 3, in <module> |
| | load_data('missing.txt') |
| | ~~~~~~~~~^^^^^^^^^^^^^^^ |
| | File "<python-input-2>", line 6, in load_data |
| | raise IOError(f'Could not load data from {filename}') from eo |
| | OSError: Could not load data from missing.txt |
This design accomplishes three important goals:
It shifts responsibility for handling the error to the caller, which is presumably in a better position to decide what to do next.
It repackages a low-level, implementation-specific FileNotFoundError as a higher-level, more abstract IOError, which might make more sense in the broader context of the program.
It preserves the tracebacks of the original errors so you can see the full caller chain later.
The from eo syntax in the raise statement copies details from the original exception object (eo) into the new one, where you can access them through the __cause__ attribute. The function traceback.print_exception shows the full call stack for post-mortem analysis.
Use exception chaining when you want to abstract away implementation details from the caller but still retain them for debugging, logging, or troubleshooting.