Worth noting is that there is an existing loop-breaking mechanism,
but only for the newest exception being raised. In particular, option (4)
is actually the current behavior if the the most recent exception
participates in a cycle:
Python 3.9.0b1
>>> A, B, C, D, E = map(Exception, "ABCDE")
>>> A.__context__ = B
>>> B.__context__ = C
>>> C.__context__ = D
>>> D.__context__ = E
>>> try:
... raise A
... except Exception:
... raise C
...
Exception: B
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "<stdin>", line 2, in <module>
Exception: A
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "<stdin>", line 4, in <module>
Exception: C
This cycle-breaking is not due to any magic in the ``PyException_SetContext()``,
which is currently a basic one-liner, but instead comes from
``_PyErr_SetObject`` in errors.c, which has something along the lines of:
def _PyErr_SetObject(new_exc):
top = existing_topmost_exc()
if top is None:
# no context
set_top_exception(new_exc)
return
# convert new_exc class to instance if applicable.
...
if top is new_exc:
# already on top
return
e = top
while True:
context = e.__context__
if context is None:
# no loop
break
if context is new_exc:
# unlink the existing exception
e.__context__ = None
break
e = context
new_exc.__context__ = top
set_top_exception(new_exc)
The only trouble comes about when there is a "rho-shaped" linked list,
in which we have a cycle not involving the new exception being raised.
For instance,
Raising A on top of (B -> C -> D -> C -> D -> C -> ...)
results in an infinite loop.
Two possible fixes would be to either (I) use a magical ``__context__``
setter to ensure that there is never a rho-shaped sequence, or (II)
allow arbitrary ``__context__`` graphs and then correctly handle
rho-shaped sequences in ``_PyErr_SetObject`` (i.e. at raise-time).
Fix type (I) could result in surprising things like:
>>> A = Exception()
>>> A.__context__ = A
>>> A.__context__ is None
True
so I propose fix type (II). This PR is such a fix:
https://github.com/python/cpython/pull/20539
It basically extends the existing behavior (4) to the rho-shaped case.
It also prevents the cycle-detecting logic from sitting in two places
(both _PyErr_SetObject and PyException_SetContext) and does not make any
visible functionality more magical. The only Python-visible change
should be that the infinite loop is no longer possible.
_______________________________________________
Python-Dev mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3/lists/python-dev.python.org/
Message archived at
https://mail.python.org/archives/list/[email protected]/message/R5J5JVUJX3V4DBKVLUI2SUBRD3TRF6PV/
Code of Conduct: http://python.org/psf/codeofconduct/