[Python-Dev] PEP 550 v4 (original) (raw)

Elvis Pranskevichus elprans at gmail.com
Sat Aug 26 10:58:33 EDT 2017


On Saturday, August 26, 2017 2:34:29 AM EDT Nathaniel Smith wrote:

On Fri, Aug 25, 2017 at 3:32 PM, Yury Selivanov <yselivanov.ml at gmail.com> wrote: > Coroutines and Asynchronous Tasks > --------------------------------- > > In coroutines, like in generators, context variable changes are > local> > and are not visible to the caller:: > import asyncio > > var = newcontextvar() > > async def sub(): > assert var.lookup() == 'main' > var.set('sub') > assert var.lookup() == 'sub' > > async def main(): > var.set('main') > await sub() > assert var.lookup() == 'main' > > loop = asyncio.geteventloop() > loop.rununtilcomplete(main())

I think this change is a bad idea. I think that generally, an async call like 'await asyncsub()' should have the equivalent semantics to a synchronous call like 'syncsub()', except for the part where the former is able to contain yields. Giving every coroutine an LC breaks that equivalence. It also makes it so in async code, you can't necessarily refactor by moving code in and out of subroutines. Like, if we inline 'sub' into 'main', that shouldn't change the semantics, but...

If we could easily, we'd given each normal function its own logical context as well.

What we are talking about here is variable scope leaking up the call stack. I think this is a bad pattern. For decimal context-like uses of the EC you should always use a context manager. For uses like Web request locals, you always have a top function that sets the context vars.

I think I see the motivation: you want to make await sub() and await ensurefuture(sub()) have the same semantics, right? And the latter has to create a Task

What we want is for await sub() to be equivalent to await asyncio.wait_for(sub()) and to await asyncio.gather(sub()).

Imagine we allow context var changes to leak out of async def. It's easy to write code that relies on this:

async def init(): var.set('foo')

async def main(): await init() assert var.lookup() == 'foo'

If we change await init() to await asyncio.wait_for(init()), the code will break (and in real world, possibly very subtly).

It also adds non-trivial overhead, because now lookup() is O(depth of async callstack), instead of O(depth of (async) generator nesting), which is generally much smaller.

You would hit cache in lookup() most of the time.

                            Elvis


More information about the Python-Dev mailing list