classification
Title: __context__ reset to None in nested exception
Type: behavior Stage:
Components: Versions: Python 3.6
process
Status: open Resolution:
Dependencies: Superseder:
Assigned To: Nosy List: Christopher Stelma, chris.jerdonek, iritkatriel
Priority: normal Keywords:

Created on 2017-08-15 17:43 by Christopher Stelma, last changed 2021-01-08 15:41 by iritkatriel.

Messages (3)
msg300305 - (view) Author: Christopher Stelma (Christopher Stelma) Date: 2017-08-15 17:43
When I try to re-raise an exception with a __cause__ inside a nested exception, the __context__ of the outer __context__ appears to be reset to None.

e.g.

>>> try:
...     try:
...         raise Exception('foo')
...     except Exception as foo:
...         print(foo, foo.__context__, foo.__cause__)
...         try:
...             raise Exception('bar') from foo
...         except Exception as bar:
...             print(bar, bar.__context__, bar.__context__.__context__)
...             raise foo from bar
... except Exception as foo:
...     wat = foo
...
foo None None
bar foo None
>>> print(wat, wat.__context__, wat.__context__.__context__)
foo bar None
>>> print(wat, wat.__cause__, wat.__cause__.__context__)
foo bar None
>>> print(wat, wat.__cause__, wat.__cause__.__cause__)
foo bar foo

here, between "raise foo from bar" and the end, bar.__context__ goes from foo to None.  since bar is only raised once, clearly in the context of foo (twice over), I would expect bar.__context__ to stay foo.
msg300306 - (view) Author: Christopher Stelma (Christopher Stelma) Date: 2017-08-15 17:45
crosslink to related future lib issue that led me to this:

https://github.com/PythonCharmers/python-future/issues/141
msg384676 - (view) Author: Irit Katriel (iritkatriel) * (Python triager) Date: 2021-01-08 15:41
This seems to be deliberately done here in order to prevent context cycles from forming: https://github.com/python/cpython/blob/fe6e5e7cfd68eeaa69fd1511f354a1b4d8d90990/Python/errors.c#L148

In your code you are creating a cycle where foo is the context (and cause) for bar which is the context (and cause) of the second time foo is raised. 

If you create a new instance of foo for the second raise then there is no cycle and you get what you expect:

try:
    try:
        raise Exception('foo')
    except Exception as foo:
        print("1--", foo, foo.__context__, foo.__cause__)
        try:
            raise Exception('bar') from foo
        except Exception as bar:
            print("2--", bar, bar.__context__, bar.__context__.__context__)
            raise Exception('foo2') from bar
except Exception as foo:
    wat = foo

print("3--", wat, wat.__context__, wat.__context__.__context__)
print("4--", wat, wat.__cause__, wat.__cause__.__context__)
print("5--", wat, wat.__cause__, wat.__cause__.__cause__)


Output is:

1-- foo None None
2-- bar foo None
3-- foo2 bar foo
4-- foo2 bar foo
5-- foo2 bar foo


I think the bug is in your code - you can't create an exception chain that contains the same exception instance more than once.
History
Date User Action Args
2021-01-08 15:41:40iritkatrielsetnosy: + iritkatriel
messages: + msg384676
2020-06-07 01:04:06chris.jerdoneksetnosy: + chris.jerdonek
2017-08-15 17:45:07Christopher Stelmasetmessages: + msg300306
2017-08-15 17:43:59Christopher Stelmacreate