[Python-Dev] PEP 550 v4 (original) (raw)
Elvis Pranskevichus elprans at gmail.com
Sat Aug 26 10:58:33 EDT 2017
- Previous message (by thread): [Python-Dev] PEP 550 v4
- Next message (by thread): [Python-Dev] PEP 550 v4
- Messages sorted by: [ date ] [ thread ] [ subject ] [ author ]
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
- Previous message (by thread): [Python-Dev] PEP 550 v4
- Next message (by thread): [Python-Dev] PEP 550 v4
- Messages sorted by: [ date ] [ thread ] [ subject ] [ author ]