On Fri, Oct 13, 2017 at 7:38 PM, Yury Selivanov <yselivanov.ml@gmail.com> wrote:
On Fri, Oct 13, 2017 at 11:49 AM, Koos Zevenhoven <k7hoven@gmail.com> wrote:
[..]
> This was my starting point 2.5 years ago, when Yury was drafting this status
> quo (PEP 492). It looked a lot of PEP 492 was inevitable, but that there
> will be a problem, where each API that uses "blocking IO" somewhere under
> the hood would need a duplicate version for asyncio (and one for each
> third-party async framework!). I felt it was necessary to think about a
> solution before PEP 492 is accepted, and this became a fairly short-lived
> thread here on python-ideas:

Well, it's obvious why the thread was "short-lived".  Don't mix
non-blocking and blocking code and don't nest asyncio loops.  But I
believe this new subtopic is a distraction. 

​Nesting is not the only way to have interaction between two event loops.​
​ But whenever anyone *does* want to nest two loops, they are perhaps more likely to be loops of different frameworks.​

​You believe that the semantics in async code is a distraction?
 
You should start a new
thread on Python-ideas if you want to discuss the acceptance of PEP
492 2.5 years ago.

I
​'m definitely not interested in discussing the acceptance of PEP 492.
 

[..]
> The bigger question is, what should happen when a coroutine awaits on
> another coroutine directly, without giving the framework a change to
> interfere:
>
>
> async def inner():
>     do_context_aware_stuff()
>
> async def outer():
>     with first_context():
>         coro = inner()
>
>     with second_context():
>         await coro
>
> The big question is: In the above, which context should the coroutine be run
> in?

The real big question is how people usually write code.  And the
answer is that they *don't write it like that* at all.  Many context
managers in many frameworks (aiohttp, tornado, and even asyncio)
require you to wrap your await expressions in them.  Not coroutine
instantiation.

​You know very well that I've been talking about how people usually write code etc. But we still need to handle the corner cases too.​
 

A more important point is that existing context solutions for async
frameworks can only support a with statement around an await
expression. And people that use such solutions know that 'with ...:
coro = inner()' isn't going to work at all.

Therefore wrapping coroutine instantiation in a 'with' statement is
not a pattern.  It can only become a pattern, if whatever execution
context PEP accepted in Python 3.7 encouraged people to use it.


​The code is to illustrate semantics, not an example of real code. The point is to highlight that the context has changed between when the coroutine function was called and when it is awaited. That's certainly a thing that can happen in real code, even if it is not the most typical case. I do mention this in my previous email.
 
[..]
> Both of these would have their own stack of (argument, value) assignment
> pairs, explained in the implementation part of the first PEP 555 draft.
> While this is a complication, the performance overhead of these is so small,
> that doubling the overhead should not be a performance concern.

Please stop handwaving performance.  Using big O notation:


​There is discussion on perfomance elsewhere, now also in this other subthread:


PEP 555, worst complexity for uncached lookup: O(N), where 'N' is the
total number of all context values for all context keys for the
current frame stack. 

​Not true. See the above link. Lookups are fast (*and* O(1), if we want them to be).

​PEP 555 stacks are independent of frames, BTW.​

 
For a recursive function you can easily have a
situation where cache is invalidated often, and code starts to run
slower and slower.

​Not true either. The lookups are O(1) in a recursive function, with and without nested contexts.​

​I started this thread for discussion about semantics in an async context. Stefan asked about performance in the other thread, so I posted there.

––Koos


PEP 550 v1, worst complexity for uncached lookup: O(1), see [1].

PEP 550 v2+, worst complexity for uncached lookup: O(k), where 'k' is
the number of nested generators for the current frame. Usually k=1.

While caching will mitigate PEP 555' bad performance characteristics
in *tight loops*, the performance of uncached path must not be
ignored.

Yury

[1] https://www.python.org/dev/peps/pep-0550/#appendix-hamt-performance-analysis



--
+ Koos Zevenhoven + http://twitter.com/k7hoven +