Context Suppression in Python Exceptions

From Exterior Memory
Revision as of 12:03, 19 August 2013 by MacFreek (Talk | contribs) (Created page with "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 ...")

(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to: navigation, search

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")

In Python 2, this 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

In Python 3, this 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

Similarly, in Python 3 it is possible to set the cause of an Exception:

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

In Python 3.3 is possible to suppress also 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

Note that this 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

However, this 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