[Python-Dev] PEP for allowing 'raise NewException from None'

Nick Coghlan ncoghlan at gmail.com
Fri Jan 27 06:18:49 CET 2012


On Fri, Jan 27, 2012 at 1:54 PM, Benjamin Peterson <benjamin at python.org> wrote:
> BTW, I don't really think this needs a PEP.

That's largely my influence - the discussion in the relevant tracker
item (http://bugs.python.org/issue6210) had covered enough ground that
I didn't notice that Ethan's specific proposal *isn't* a syntax
change, but is rather just a matter of giving some additional
semantics to the "raise X from Y" syntax (some of the other
suggestions like "raise as <whatever>" really were syntax changes).

So I've changed my mind to being +1 on the idea and proposed syntax of
the draft PEP, but I think there are still some details to be worked
through in terms of the detailed semantics. (The approach in Ethan's
patch actually *clobbers* the context info when "from None" is used,
and I don't believe that's a good idea. My own suggestions in the
tracker item aren't very good either, for exactly the same reason)

Currently, the raise from syntax is just syntactic sugar for setting
__cause__ manually:

>>> try:
...     1/0
... except ZeroDivisionError as ex:
...     new_exc = ValueError("Denominator is zero")
...     new_exc.__cause__ = ex
...     raise new_exc
...
Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
ZeroDivisionError: division by zero

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

Traceback (most recent call last):
  File "<stdin>", line 6, in <module>
ValueError: Denominator is zero

The context information isn't lost in that case, the display of it is
simply suppressed when an explicit cause is set:

>>> try:
...     try:
...         1/0
...     except ZeroDivisionError as ex:
...         new_exc = ValueError()
...         new_exc.__cause__ = ex
...         raise new_exc
... except ValueError as ex:
...     saved = ex
...
>>> saved.__context__
ZeroDivisionError('division by zero',)
>>> saved.__cause__
ZeroDivisionError('division by zero',)

This behaviour (i.e. preserving the context, but not displaying it by
default) is retained when using the dedicated syntax:

>>> try:
...     try:
...         1/0
...     except ZeroDivisionError as ex:
...         raise ValueError() from ex
... except ValueError as ex:
...     saved = ex
...
>>> saved.__context__
ZeroDivisionError('division by zero',)
>>> saved.__cause__
ZeroDivisionError('division by zero',)

However, if you try to set the __cause__ to None explicitly, then the
display falls back to showing the context:

>>> try:
...     1/0
... except ZeroDivisionError as ex:
...     new_exc = ValueError("Denominator is zero")
...     new_exc.__cause__ = None
...     raise new_exc
...
Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
ZeroDivisionError: division by zero

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "<stdin>", line 6, in <module>
ValueError: Denominator is zero

This happens because None is used by the exception display logic to
indicate "no specific cause, so report the context if that is set".

My proposal would be that instead of using None as the "not set"
sentinel value for __cause__, we instead use a dedicated sentinel
object (exposed to Python at least as "BaseException().__cause__", but
potentially being given its own name somewhere).

Then the display logic for exceptions would be changed to be:
- if the __cause__ is None, then don't report a cause or exception
context at all
- if the __cause__ is BaseException().__cause__, report the exception
context (from __context__)
- otherwise report __cause__ as the specific cause of the raised exception

That way we make it easy to emit nicer default tracebacks when
replacing exceptions without completely hiding the potentially useful
data that can be provided by retaining information in __context__.

I've been burnt by too much code that replaces detailed, informative
and useful error messages that tell me exactly what is going wrong
with bland, useless garbage to be in favour of an approach that
doesn't even set the __context__ attribute in the first place. If
__context__ is always set regardless, and then __cause__ is used to
control whether or not __context__ gets displayed in the standard
tracebacks, that's a much more flexible approach.

Cheers,
Nick.

-- 
Nick Coghlan   |   ncoghlan at gmail.com   |   Brisbane, Australia


More information about the Python-Dev mailing list