[Python-ideas] Allow Context Managers to Support Suspended Execution

David Allemang allemang.d at gmail.com
Wed Oct 31 22:52:44 EDT 2018


I do not think there is currently a good way for Context Managers to
support suspended execution, as in await or yield. Both of these
instructions cause the interpreter to leave the with block, yet no
indication of this (temporary) exit or subsequent re-entrance is given
to the context manager. If the intent of a Context Manager is to say
"no matter how this block is entered or exited, the context will be
correctly maintained", then this needs to be possible.

I would propose magic methods __suspend__ and __resume__ as companions
to the existing __enter__ and __exit__ methods (and their async
variants). __suspend__, if present, would be called upon suspending
execution on an await or yield statement, and __resume__, if present,
would be called when execution is resumed. If __suspend__ or
__resume__ are not present then nothing should be done, so that the
behavior of existing context managers is preserved.

Here is an example demonstrating the issue with await:
https://gist.github.com/allemangD/bba8dc2d059310623f752ebf65bb6cdc
and one with yield:
https://gist.github.com/allemangD/f2534f16d3a0c642c2cdc02c544e854f

The context manager used is clearly not thread-safe, and I'm not
actually sure how to approach a thread-safe implementation with the
proposed __suspend__ and __resume__ - but I don't believe that
introducing these new methods would create any issues that aren't
already present with __enter__ and __exit__.

It's worth noting that the context manager used in those examples is,
essentially, identical contextlib's redirect_stdout and decimal's
localcontext managers. Any context manager such as these which modify
global state or the behavior of global functions would benefit from
this. It may also make sense to, for example, have the __suspend__
method on file objects flush buffers without closing the file, similar
to their current __exit__ behavior, but I'm unsure what impact this
would have on performance.

It is important, though, that yield and await not use __enter__ or
__exit__, as not all context-managers are reusable. I'm unsure  what
the best term would be to describe this type of context, as the
documentation for contextlib already gives a different definition for
"reentrant" - I would then call them "suspendable" contexts. It would
make sense to have an @suspendable decorator, probably in contextlib,
to indicate that a context manager can use __enter__ and __exit__
methods rather than __suspend__ and __resume__. All it would need to
do is define __suspend__ to call __enter__() and __resume__ to call
__exit__(None, None, None).

It is also important, since __suspend__ and __resume__ would be called
after a context is entered but before it is exited, that __suspend__
not accept any parameters and that __resume__ not use its return
value. __suspend__ could not be triggered by an exception, only by a
yield or await, and __resume__ could not have its return value named
with as.

Thanks,

David


More information about the Python-ideas mailing list