On 13 October 2017 at 10:56, Guido van Rossum <guido@python.org> wrote:
I'm out of energy to debate every point (Steve said it well -- that decimal/generator example is too contrived), but I found one nit in Nick's email that I wish to correct.

On Wed, Oct 11, 2017 at 1:28 AM, Nick Coghlan <ncoghlan@gmail.com> wrote:
As a less-contrived example, consider context managers implemented as generators.

We want those to run with the execution context that's active when they're used in a with statement, not the one that's active when they're created (the fact that generator-based context managers can only be used once mitigates the risk of creation time context capture causing problems, but the implications would still be weird enough to be worth avoiding).

Here I think we're in agreement about the desired semantics, but IMO all this requires is some special casing for @contextlib.contextmanager. To me this is the exception, not the rule -- in most *other* places I would want the yield to switch away from the caller's context.
 
For native coroutines, we want them to run with the execution context that's active when they're awaited or when they're prepared for submission to an event loop, not the one that's active when they're created.

This caught my eye as wrong. Considering that asyncio's tasks (as well as curio's and trio's) *are* native coroutines, we want complete isolation between the context active when `await` is called and the context active inside the `async def` function.

The rationale for this behaviour *does* arise from a refactoring argument:

   async def original_async_function():
        with some_context():
            do_some_setup()
            raw_data = await some_operation()
            data = do_some_postprocessing(raw_data)

Refactored:

   async def async_helper_function():
        do_some_setup()
        raw_data = await some_operation()
        return do_some_postprocessing(raw_data)

   async def refactored_async_function():
        with some_context():
            data = await async_helper_function()

However, considering that coroutines are almost always instantiated at the point where they're awaited, I do concede that creation time context capture would likely also work out OK for the coroutine case, which would leave contextlib.contextmanager as the only special case (and it would turn off both creation-time context capture *and* context isolation).

Cheers,
Nick.

--
Nick Coghlan   |   ncoghlan@gmail.com   |   Brisbane, Australia