Awsome Python - chained exceptions

Terry Reedy tjreedy at udel.edu
Tue Feb 12 16:13:15 CET 2013


On 2/12/2013 1:15 AM, Steven D'Aprano wrote:
> 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.

You do not need Rick to justify such an informative post.

> 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__.

Some thought was given to having only one special attribute, but in the 
end it was decided to have __context__ be the actual context and 
__cause__ be the programmer set and displayed '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
>
The new features are explained in the Library manual, Ch. 5, Exceptions, 
but without so many clear examples. The 'from None' option has not yet 
been added to the Language reference section on raise statements (an 
issue on the tracker), so it is easy to miss if one does not also read 
the Library chapter.
>
> You can read more about exception chaining here:
>
> http://www.python.org/dev/peps/pep-3134/
> http://www.python.org/dev/peps/pep-0409/

-- 
Terry Jan Reedy




More information about the Python-list mailing list