The Most Diabolical Python Antipattern

Ian Kelly ian.g.kelly at gmail.com
Fri Jan 30 10:21:09 EST 2015


On Fri, Jan 30, 2015 at 3:00 AM, Marko Rauhamaa <marko at pacujo.net> wrote:
> Marko Rauhamaa <marko at pacujo.net>:
>
>>>> Surprisingly this variant could raise an unexpected exception:
>>>>
>>>> ==============================
>>>>      try:
>>>>          do_interesting_stuff()
>>>>      except ValueError:
>>>>          try:
>>>>              log_it()
>>>>          finally:
>>>>              raise
>>>> ==============================
>>>>
>>>> A Python bug?
>> [...]
>> My Python did do exception chaining, but the problem is the surface
>> exception changes, which could throw off the whole error recovery.
>
> BTW, the code above can be fixed:
>
> ==============================
>   try:
>       do_interesting_stuff()
>   except ValueError as e:
>       try:
>           log_it()
>       finally:
>           raise e
> ==============================
>
> Now the surface exception is kept and the subsidiary exception is
> chained to it.
>
> I'm a bit baffled why the two pieces of code are not equivalent.

The bare raise re-raises the most recent exception that is being
handled. The "raise e" raises that exception specifically, which is
not the most recent in the case of a secondary exception.

Note that the exceptions are actually chained *in reverse*; the
message falsely indicates that the secondary exception was raised
first, and the primary exception was raised while handling it, e.g.:

Traceback (most recent call last):
  File "<stdin>", line 5, in <module>
TypeError

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "<stdin>", line 7, in <module>
  File "<stdin>", line 2, in <module>
ValueError

That's because "raise e" causes the exception e to be freshly raised,
whereas the bare "raise" merely makes the existing exception context
active again.

It's interesting to note here that although the exception retains its
original traceback information (note the two separate lines in the
traceback), it is not chained again from the TypeError. One might
expect to actually see the ValueError, followed by the TypeError,
followed by the ValueError in the chain. That doesn't happen because
the two ValueErrors raised are actually the same object, and Python is
apparently wise enough to break the chain to avoid an infinite cycle.



More information about the Python-list mailing list