New submission from Nathaniel Smith <n...@pobox.com>:

Inside 'except' and 'finally' blocks, the interpreter keeps track of the 
'active exception in the thread-state. It can be introspected via 
`sys.exc_info()`, it enables bare `raise`, and it triggers implicit context 
propagation.

In an odd bit of synchronicity, I recently bumped into two completely separate 
cases where Python code would benefit from being able to control this state 
directly:

- in the flat exception groups discussion [1], I noted that it would be useful 
if concurrency libraries could propagate this state into child tasks

- in bpo-27089, regarding some gnarly code in contextlib.ExitStack that 
simulates a set of nested try/finally blocks by running callback with the wrong 
exception context, and then introspecting the result and trying to fix it up 
after the fact. It turns out this code is difficult to understand and subtly 
buggy -- the PR at gh-27089 fixes one of the bugs here, but there are others 
[2]. It would be much simpler if it could just set the correct `exc_info` 
before calling each cleanup function, and then no fixups would be needed things 
would be set up correctly in the first place.

[1] 
https://discuss.python.org/t/flat-exception-groups-alternative-to-pep-654/10433

[2] https://github.com/python/cpython/pull/27089#issuecomment-932892687

But, currently, the *only* way to make an exception 'active' like this is to 
raise it and then catch it again. And this has some significant limitations:

- It unconditionally mutates the exception -- in particular both __context__ 
and __traceback__ are modified

- The "active exception" has type Optional[BaseException], since 
try/raise/except is block syntax, and you can't `raise None`. So if you want to 
propagate an arbitrary `sys.exc_info()` into a child task or into a simulated 
`finally` block, then you need two separate code paths depending on whether 
`sys.exc_info()[1]` is None or not.

- technically I think you can work around both of these issues with enough 
effort... but since try/raise/except is block syntax, the workarounds can't be 
hidden inside a function; you have to inline them into each usage site.

So... I'm thinking maybe we should have some stupid-simple, sharp-edged API to 
set `sys.exc_info()` from Python. Like, idk, `sys.set_exc_info(...)`, as a 
trivial wrapper around `PyErr_Restore`.

My main uncertainty is that I know the code for handling the exception state is 
quite complex, between the "exception stack", the tricky optimizations around 
partially-initialized exc_info tuples, pushing/popping the exc state in 
ceval.c, the tricks generators/coroutines use to save/restore exc_info across 
yields, etc. There might be some hidden dragons in here that require careful 
handling, and maybe it'll turn out we need to push/pop an exception instead of 
just setting it, or something like that.

But does the core idea make sense? Anyone aware of any dragons off the top of 
your head?

----------
messages: 403121
nosy: Mark.Shannon, gvanrossum, iritkatriel, ncoghlan, njs, yselivanov
priority: normal
severity: normal
status: open
title: Provide a more convenient way to set an exception as "active", from 
Python code
type: enhancement

_______________________________________
Python tracker <rep...@bugs.python.org>
<https://bugs.python.org/issue45361>
_______________________________________
_______________________________________________
Python-bugs-list mailing list
Unsubscribe: 
https://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com

Reply via email to