General

Python 3 Exception Chaining

Exception chaining solves two problems in Python 2.

1. Swallowed Exceptions

If, while handling exception A, exception B inadvertently occurred, exception A was completely lost.

# Python 2
try:
    raise ValueError('original problem')
except Exception as exc:
    4/0  # bad calculation while handling the original problem

Sadly, the original problem is nowhere to be found in our Python 2 stack trace:

Traceback (most recent call last):
  File "/tmp/t1.py", line 4, in <module>
    4/0
ZeroDivisionError: integer division or modulo by zero

2. Preserving Tracebacks

Sometimes it’s useful, when catching an exception, to raise a new exception (e.g., a custom error type). In Python 2, the original exception could only be preserved to the extent that you could explicitly include it in the new exception’s message or args. It’s annoying enough to have to encode the original exception’s message into your new exception; but if you want to pass along its stack trace, then you’re pretty much out of luck. (Unless you want to add several confusing lines to your except block; even then, you’d have to pre-format the traceback in a way that you knew would be appropriate for use downstream. This is not a good situation to find yourself in.)

# Python 2
try:
    # some code that raises an exception
except Exception as exc:
    # the traceback of "exc" will be lost once we raise the following exception
    raise MyErrorType('The flurble operation failed due to {}'.format(exc))

Implicit Exception Chaining

Python implicitly chains any exception that occurs while handling another exception. This solves problem #1:

# Python 3 (but same code as Python 2 version above)
try:
    raise ValueError('original problem')
except Exception as exc:
    4/0  # bad calculation while handling the original problem

Without any modification to the code, note how Python 3 keeps track of the exception chain for us:

Traceback (most recent call last):
  File "/tmp/t1.py", line 2, in <module>
    raise ValueError()
ValueError

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/tmp/t1.py", line 4, in <module>
    4/0
ZeroDivisionError: division by zero

Explicit Exception Chaining

If we explicitly state that some exception was the proximate cause of another, Python 3 will remember the ensuing causal chain for us:

# Python 3
try:
    a = 3
    b = 0
    x = a / b  # uh-oh

except Exception as exc:
    raise ValueError('Some data was bad') from exc

(Note the “from exc” on the last line.)

The chain of exceptions is preserved and displayed, each with its own stack trace:

Traceback (most recent call last):
  File "/tmp/t.py", line 20, in <module>
    x = a / b  # uh-oh
ZeroDivisionError: division by zero

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

Traceback (most recent call last):
  File "/tmp/t.py", line 23, in <module>
    raise ValueError('Some data was bad') from exc
ValueError: Some data was bad

Happy chaining!

Ron

https://www.ronrothman.com/public/about+me.shtml

Share
Published by
Ron

Recent Posts

Python 3 Rounding Surprise

I discovered this subtle change when one of my unit tests mysteriously failed once I…

4 years ago

Safe Password Storage – How Websites Get It Wrong

Here's the recording of my talk "15 minutes w/ Beeswax: Safe Password Storage - How…

4 years ago

Python at Scale: Concurrency at Beeswax

My presentation from the NYC Python Meetup: Python at Scale; Concurrency at Beeswax. Concurrent Python…

4 years ago

Python Dependencies The Right Way

My BazelCon 2019 lightning talk, Python Dependencies The Right Way*, has been posted. Please excuse…

4 years ago

Python 3 f-strings

One of my favorite features of Python 3 is f-strings (Formatted String Literals). Each of…

4 years ago

mtwsgi: A Multithreaded Python WSGI Implementation

I wanted to combine the simplicity of Python's built-in WSGI server with the benefits of…

11 years ago

This website uses cookies.