Context Suppression in Python Exceptions

From Exterior Memory
Jump to: navigation, search

Catching and re-raising a different exception

It is sometimes useful to catch one exception and raise another exception. For example, a higher software layer may not care where exactly an algorithm fails, but merely that the input is invalid. Or it may not care which parsing routine fails, just that the data was truncated.

For example:

def process(iterable):
    try:
        x = next(iterable)
    except StopIteration:
        raise ValueError("can't process empty iterable")
    continue_processing()
def reciprocal(x):
    try:
        return 1/x
    except ZeroDivisionError:
        raise ValueError("divisor must not be zero")

Python 2 Output

In Python 2, reciprocal(0) only displays the inner exception:

Traceback (most recent call last):
  File "test.py", line 12, in <module>
    reciprocal(0)
  File "test.py", line 10, in reciprocal
    raise ValueError("divisor must not be zero")
ValueError: divisor must not be zero

Python 3 Output: Exception Context

In Python 3, reciprocal(0) displays the context of the exception as well:

Traceback (most recent call last):
  File "test.py", line 8, in reciprocal
    return 1/x
ZeroDivisionError: division by zero

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "test.py", line 12, in <module>
    reciprocal(0)
  File "test.py", line 10, in reciprocal
    raise ValueError("divisor must not be zero")
ValueError: divisor must not be zero

Python 3 Output: Exception Cause

In Python 3 it is possible to set the cause of an Exception, which displays a similar message:

raise ValueError("divisor must not be zero") from ZeroDivisionError('division by zero')

which prints:

ZeroDivisionError: division by zero

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "test.py", line 13, in <module>
    raise ValueError("divisor must not be zero") from ZeroDivisionError('division by zero')
ValueError: divisor must not be zero

Suppressing Context in Python 3.3

In Python 3.3 is possible to suppress the context by setting the cause to None:

def reciprocal(x):
    try:
        return 1/x
    except ZeroDivisionError:
        raise ValueError("divisor must not be zero") from None

which prints:

Traceback (most recent call last):
  File "test.py", line 12, in <module>
    reciprocal(0)
  File "test.py", line 10, in reciprocal
    raise ValueError("divisor must not be zero") from None
ValueError: divisor must not be zero

Python 2 Valid Syntax for Suppressing Context

raise Exception from Cause is not valid Python 2 syntax. If you want to write your code such that it works with Python 2, the following syntax is equivalent:

def reciprocal(x):
    try:
        return 1/x
    except ZeroDivisionError:
        _exception = ValueError("divisor must not be zero")
        _exception.__cause__ = None
        raise _exception

Suppressing Context in Python 3.1 and 3.2

The above code will still print the exception in Python 3.1 and Python 3.2. If you want to suppress the context there too, the following hack-ish solutions works accross all Python versions (2.6, 2.7 and 3):

_exception = None
try:
    i = 1/0
except ZeroDivisionError as e:
    _exception = ValueError("divisor must not be zero")
finally:
    if _exception:
        raise _exception

Further Reading