PyRight rightly complains when ExitStack is used:
with ExitStack() as stack: for x in l: stack.enter_context(x) y = f() print(y) # y is possibly unbound!
This is because there’s no way to know whether one of the context managers didn’t suppress an exception raised by another context manager.
How should this code be made safe?
You might try:
y = some_sentinel with ExitStack() as stack: for x in l: stack.enter_context(x) y = f() assert y is not some_sentinel print(y) # no error
But this gets annoying when annotating y, and when there are many variables. You might try adding a flag:
flag = False with ExitStack() as stack: for x in l: stack.enter_context(x) y = f() flag = True assert flag print(y) # y is possibly unbound!
but this doesn’t fix the type error.
Would it make sense to add either a flag or a parameter to ExitStack that says that it cannot exit early? Either
ExitStack(on_early_exit=RuntimeError("blah"))? Although I’m not sure how the annotation for
ExitStack would work to let type checkers know that
__exit__ always return
False when that flag is given. I guess a final option would be to add something like:
In : class SafeExitStack(ExitStack): ...: def __exit__( ...: self, ...: __exc_type: type[BaseException] | None, ...: __exc_value: BaseException | None, ...: __traceback: TracebackType | None ...: ) -> Literal[False]: ...: retval = super().__exit__(__exc_type, __exc_value, __traceback) ...: if retval: ...: raise RuntimeError("Internal error: suppressed excpeption") ...: return False
This would give a simple transformation of the above code to silence PyRight.