Awsome Python - chained exceptions
Steven D'Aprano
steve+comp.lang.python at pearwood.info
Tue Feb 12 01:15:29 EST 2013
As an antidote to the ill-informed negativity of Ranting Rick's
illusionary "PyWarts", I thought I'd present a few of Python's more
awesome features, starting with exception contexts.
If you've ever written an exception handler, you've probably written a
*buggy* exception handler:
def getitem(items, index):
# One-based indexing.
try:
return items[index-1]
except IndexError:
print ("Item at index %d is missing" % index - 1) # Oops!
Unfortunately, when an exception occurs inside an except or finally
block, the second exception masks the first, and the reason for the
original exception is lost:
py> getitem(['one', 'two', 'three'], 5) # Python 2.6
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 6, in getitem
TypeError: unsupported operand type(s) for -: 'str' and 'int'
But never fear! In Python 3.1 and better, Python now shows you the full
chain of multiple exceptions, and exceptions grow two new special
attributes: __cause__ and __context__.
If an exception occurs while handling another exception, Python sets the
exception's __context__ and displays an extended error message:
py> getitem(['one', 'two', 'three'], 5) # Python 3.1
Traceback (most recent call last):
File "<stdin>", line 4, in getitem
IndexError: list index out of range
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 6, in getitem
TypeError: unsupported operand type(s) for -: 'str' and 'int'
Python 3 also allows you to explicitly set the exception's __cause__
using "raise...from" syntax:
py> try:
... len(None)
... except TypeError as e:
... raise ValueError('bad value') from e
...
Traceback (most recent call last):
File "<stdin>", line 2, in <module>
TypeError: object of type 'NoneType' has no len()
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
File "<stdin>", line 4, in <module>
ValueError: bad value
Note the slight difference in error message. If both __cause__ and
__context__ are set, the __cause__ takes priority.
Sometimes you actually want to deliberately catch one exception and raise
another, without showing the first exception. A very common idiom in
Python 2:
try:
do_work()
except SomeInternalError:
raise PublicError(error_message)
Starting with Python 3.3, there is now support from intentionally
suppressing the __context__:
py> try:
... len(None)
... except TypeError:
... raise ValueError('bad value') from None # Python 3.3
...
Traceback (most recent call last):
File "<stdin>", line 4, in <module>
ValueError: bad value
You can read more about exception chaining here:
http://www.python.org/dev/peps/pep-3134/
http://www.python.org/dev/peps/pep-0409/
--
Steven
More information about the Python-list
mailing list