bpo-45711: Change exc_info related APIs to derive type and traceback from the exception instance by iritkatriel · Pull Request #29780 · python/cpython

(I am still concerned about something else -- sometimes "raise e" and "raise" don't do the same thing even if "e" is the exception being handled. But that is not affected by these changes -- that difference will persist, and arguably it is correct (though it surprises me occasionally). Even if we wanted to change it, we couldn't, because it is not an obscure corner case -- the semantics are clearly described. Please ignore my mumbling here.)

A related story: After we moved our system from python 2 to python 3 we got bug reports about nonsensical tracebacks. Turned out we had something like this:

import sys
import traceback

class Calc:
  def __init__(self):
     self.value = None
     self.exc_info = None

  def calc(self):
     raise TypeError(12)

  def calc_and_cache(self):
    try:
      self.value = self.calc()
      return self.value
    except Exception as e:
      self.exc_info = sys.exc_info()
      raise

  def get_cached_value(self):
    if self.exc_info is not None:
      raise self.exc_info[1]
    return self.value

c = Calc()

print('------ 1 ------')
try:
   c.calc_and_cache()
except Exception as e:
   traceback.print_exception(e)

print('------ 2 ------')
try:
   c.get_cached_value()
except Exception as e:
   traceback.print_exception(e)

The two tracebacks were the same in python 2, but in python 3 you get:

------ 1 ------
Traceback (most recent call last):
  File "/Users/iritkatriel/src/cpython-1/xx.py", line 29, in <module>
    c.calc_and_cache()
    ^^^^^^^^^^^^^^^^^^
  File "/Users/iritkatriel/src/cpython-1/xx.py", line 14, in calc_and_cache
    self.value = self.calc()
                 ^^^^^^^^^^^
  File "/Users/iritkatriel/src/cpython-1/xx.py", line 10, in calc
    raise TypeError(12)
    ^^^^^^^^^^^^^^^^^^^
TypeError: 12
------ 2 ------
Traceback (most recent call last):
  File "/Users/iritkatriel/src/cpython-1/xx.py", line 35, in <module>
    c.get_cached_value()
    ^^^^^^^^^^^^^^^^^^^^
  File "/Users/iritkatriel/src/cpython-1/xx.py", line 22, in get_cached_value
    raise self.exc_info[1]
    ^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/iritkatriel/src/cpython-1/xx.py", line 29, in <module>
    c.calc_and_cache()
    ^^^^^^^^^^^^^^^^^^
  File "/Users/iritkatriel/src/cpython-1/xx.py", line 14, in calc_and_cache
    self.value = self.calc()
                 ^^^^^^^^^^^
  File "/Users/iritkatriel/src/cpython-1/xx.py", line 10, in calc
    raise TypeError(12)
    ^^^^^^^^^^^^^^^^^^^
TypeError: 12

The fix was to change the raise to raise self.exc_info[1].with_traceback(self.exc_info[2]).