On Saturday, August 26, 2017 2:34:29 AM EDT Nathaniel Smith wrote:
On Fri, Aug 25, 2017 at 3:32 PM, Yury Selivanov
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 = new_context_var()
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.get_event_loop() loop.run_until_complete(main())
I think this change is a bad idea. I think that generally, an async call like 'await async_sub()' should have the equivalent semantics to a synchronous call like 'sync_sub()', 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 ensure_future(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