On 2020-06-13 2:32 a.m., Kyle Stanley wrote:
> An await outside async def, would map to awaitable.run().

> A call to baz() would thus create a coroutine foo(), and then run() it.

-1. The `await` expression already has a well-defined purpose of suspending the coroutine it's used within until the awaitable is completed. I think this change in behavior would be rather confusing, and results in minimal (if any) practical benefit since you can already accomplish the main intended behavior of effectively running coroutines as blocking subroutines (see next section).

It'd also let you force blocking mode by calling .run() directly, whereas "await" would let the context make the decision for you.


> In particular, this could allow one to turn any arbitrary async coroutine into a blocking subroutine, as long as it supports this run() protocol. This would be great for refactoring existing blocking code into async - just use stuff as if it's sync, then when you're done, switch up your function with async def, and change relevant calls into it into "await" calls. Rinse and repeat until you're ready to add a top-level event loop.

This can already be effectively accomplished by gradually converting the synchronous code into coroutines and running them individually with asyncio's loop.run_until_complete(coro()) (which blocks until finished) from a subroutine while the other parts of the code are still transitioning to async. However, the bulk of the effort is considering the architecture of the program, specifically analyzing the parts that need to be completed sequentially vs those that can be completed concurrently, and then figuring out exactly when/where the results are needed. So, I don't think that making an await expression outside of a coroutine function an alias to an awaitable.run() and effectively doing the same thing as loop.run_until_complete() would save substantial development time, and it adds additional unneeded complexity to the await expression for little to no practical benefit.

Also, I very much appreciate knowing that when I see "await" used, I'm certain it's being called within a coroutine and more importantly, the expected behavior. Having to check whether the function is awaited in is a coroutine or a subroutine to determine the behavior could prove to be quite cumbersome in non-trivial programs. A significant part of why await was added in the first place was to avoid the ambiguity present with "yield from". In general, I think there's a significant advantage in having a singular purpose for a keyword, rather than changing based on where it's used.


There's a huge difference between "yield from" and "await". Additionally, what if I'm not using asyncio?

> Optionally, this would also allow us to deprecate blocking APIs (like open(), etc

Huge -1 for deprecating the existing blocking APIs. Although converting some IO-bound functions to asynchronous equivalents can yield significant performance benefits (when structured optimally), it's not always worthwhile to do so. Particularly in situations where the underlying OS call doesn't have asynchronous support (frequently the case for async file system operations), or when the intended use case for the program wouldn't benefit from the performance improvements enough to justify the added complexity and development time. So blocking IO-bound functions still have a significant purpose, and should definitely __not__ be deprecated. Even if there was a working automatic conversion process to place an "await" before each of the calls, it would still cause a lot of breakage and add additional noise to the code that wouldn't provide additional useful context.

Also, consider the amount of times that various Python tutorials and textbooks use open() that would all have to be rewritten because it no longer works without "await" before it. Realistically speaking, I don't think there's any chance of something as commonly used as open() being deprecated, regardless how far out the removal would be. Even if/when async file system support at the OS level becomes the mainstream (which it currently isn't), I still don't think it would be worth breaking open().

As I said previously, you can still guarantee blocking behaviour by explicitly calling .run(), instead of using await. There is a lot of friction in refactoring to async/await, including asyncio being suboptimal and the fact that you can't really call async functions (but in blocking mode) from non-async code.


> and asyncio, and merge them into one TBD API. Perhaps as part of Python 4.

Even if I agreed with the main proposal, I would still be against this part. Stdlib module deprecation is a very long process that has to be strongly justified, considering the amount of breakage that would occur from its eventual removal. So even with the assumption that the proposal would provide some real benefit in the transition from sync to async (which I disagree with), I don't think this would come even close to justifying a full deprecation of asyncio into a separate "TBD API".

Thanks for sharing the idea Soni, but I would personally be opposed to every component of the proposal.

What's the point of having async, if it has to be such a massive pain to refactor my existing code into? Some of the issues caused by only partially switching to async, namely that you have to go out of your way to call an async function from non-async code, feel to me like unnecessary friction. I understand and love having 2 calling conventions, but, heck even having coroutines carry a run() method would be better than what we have today.

And then there's the issue of nested event loops. It just makes things even more problematic in hybrid codebases. :/

Consider my proposal the best of both worlds: you get all the benefits of async/await, and all the benefits of blocking APIs, and you get to choose whether to use autodetection or explicitly force the latter. (you could also force the existence of a nested event loop, but I don't think that'd be a good idea.)


On Fri, Jun 12, 2020 at 7:20 PM Soni L. <fakedme+py@gmail.com> wrote:


On 2020-06-12 5:47 p.m., J. Pic wrote:
Hi all,

Currently, you can not use await outside an async function, the following code:

  async def lol():
    return 'bar'

  def test():
    return await lol()

  print(test())

Will fail with: SyntaxError: 'await' outside async function

Of course, you can use asyncio.run and then it works fine:

  import asyncio

  async def lol():
    return 'bar'

  def test():
    return asyncio.run(lol())

  print(test())

Why not make using await do asyncio.run "behind the scenes" when called outside async function ?

Thank you in advance for your replies

What if we extend awaitables to be optionally "runnable"?

An await outside async def, would map to awaitable.run(). In the specific case of coroutines, this would also automatically propagate down. E.g.

async def foo():
  await bar()

def baz()
  await foo()

A call to baz() would thus create a coroutine foo(), and then run() it. By my proposed semantics, the await bar() would also call run() on the result of bar()!

In particular, this could allow one to turn any arbitrary async coroutine into a blocking subroutine, as long as it supports this run() protocol. This would be great for refactoring existing blocking code into async - just use stuff as if it's sync, then when you're done, switch up your function with async def, and change relevant calls into it into "await" calls. Rinse and repeat until you're ready to add a top-level event loop.

Optionally, this would also allow us to deprecate blocking APIs (like open(), etc) and asyncio, and merge them into one TBD API. Perhaps as part of Python 4. (e.g. turn existing open() into await open(), particularly in non-async-def. I *think* this could be done automatically as part of some sort of "3to4", even.)


--


_______________________________________________
Python-ideas mailing list -- python-ideas@python.org
To unsubscribe send an email to python-ideas-leave@python.org
https://mail.python.org/mailman3/lists/python-ideas.python.org/
Message archived at https://mail.python.org/archives/list/python-ideas@python.org/message/LOCYSYVRKXI45QQJOLYGZV6H2CBYTB7F/
Code of Conduct: http://python.org/psf/codeofconduct/

_______________________________________________
Python-ideas mailing list -- python-ideas@python.org
To unsubscribe send an email to python-ideas-leave@python.org
https://mail.python.org/mailman3/lists/python-ideas.python.org/
Message archived at https://mail.python.org/archives/list/python-ideas@python.org/message/Y73VH53OXZZ2GDWRVGRX2B76U3TSHYZF/
Code of Conduct: http://python.org/psf/codeofconduct/