📜 ⬆️ ⬇️

Python 3.5; async / await

Quietly and quietly (c), Python version 3.5 came out ! And, of course, one of the most interesting innovations of the release is the new syntax of coroutines using the async / await keywords, later in the article about it.

A superficial review of "PEP 0492 - Coroutines with async and await syntax" at first left me with the question "Why is this necessary." Coroutines are satisfactorily implemented on extended generators, and at first glance it may seem that it all came down to replacing yield from with await , and the decorator creating the coroutine with async . You can add the feeling that everything is done solely for use with the asyncio module.

But this, of course, is not so, the subject is deeper and more interesting.

coroutine

The main thing, probably, is that the coroutine in Python is now a special object of native coroutine , and not in some special way designed generator or something else. This object has methods and functions of the standard library for working with it. That is, now it is an object defined as part of the language.
')
await

Unfortunately, I didn’t find a short definition in the documentation and PEP for which this new keyword was entered. I would venture to formulate it myself: The await keyword indicates that when the expression following it is executed, it is possible to switch from the current coroutine to another or to the main execution thread.
Accordingly, the expression after await is also not simple, it should be an awaitable object.

awaitable object

There are three options for awaitable objects:

An example of how to write your own awaitable object was not found in the PEP or in the documentation, at least at the time of this writing this is not the case. This flaw will be corrected below.)

async

The PEP has a dozen paragraphs with the headings "Why ..." and "Why not ...". Almost all of them are devoted to the question of why this keyword is used in this way, and not in any other way. Indeed, async def looks weird in the code, and does it cause reflections on the subject of the “pythonic way”? On the other hand, it is clear that they wanted some more holistic picture, since there is also async for and async with .

In the definitions given in the PEP, it is written that “asynchronous code” can be invoked in magic methods. It seems to me that “switching execution from the current coroutine” is a more correct option. The use of the term "asynchronous code" can be misleading, because "asynchronous code" is often implemented on callback functions, and this is a slightly different topic.

Examples of the use of asynchronous iterators and the context of managers in the documentation and PEP are sufficient, usecase is generally understandable and everything is logical. One thing is not clear - why use versions of magic methods with different names, because they are still declared using `async def`. Apparently, this is something related to the peculiarities of implementation, I do not see another explanation.

How to cook it?


The study of a new language feature or library quickly rests on the question of how and where to use it. And a deeper study, in my opinion, should be continued on a practical example. For me, if the topic is related to coroutines, asynchrony, and the like, such a practical example is the writing of a hellowword using an event-driven approach. The task is formulated as follows: “The call to the sleep function should stop the execution of the coroutine for a certain time.”

Coroutines and event-driven are perfectly combined, in my other article in more detail, why I think so. And an example of this kind will show us well almost all the possibilities and nuances of using coroutines.

Suppose that we have an event dispatcher running in the main execution thread, which, when an expected event occurs, calls the callback function. Then the practical implementation may be:

It can be coded like this:
from time import time from collections import deque from tornado.ioloop import IOLoop current = deque() class sleep(object): def __init__(self, timeout): self.deadline = time() + timeout def __await__(self): def swith_to(coro): current.append(coro) coro.send(time()) IOLoop.instance().add_timeout(self.deadline, swith_to, current[0]) current.pop() return (yield) def coroutine_start(run, *args, **kwargs): coro = run(*args, **kwargs) current.append(coro) coro.send(None) if __name__ == '__main__': async def hello(name, timeout): while True: now = await sleep(timeout) print("Hello, {}!\tts: {}".format(name, now)) coroutine_start(hello, "Friends", 1.0) coroutine_start(hello, "World", 2.5) IOLoop.instance().start() 

As you can see, the code is short, fairly understandable and, by the way, working. I will describe the main points in it:
  1. As the event dispatcher, tornado.ioloop.IOLoop is used to comment on my opinion there is nothing special.
  2. The sleep class implements an awaitable object, its function is to transfer control to the event dispatcher, having previously configured it to call callback after a specified period of time.
  3. The callback function is defined as a closure, but in this case it does not matter. Its purpose is to simply switch the execution back to a coroutine with the transfer of the current time. Switching the execution to a coroutine is done by calling its send method or throw method to switch with throwing an exception.
  4. The purpose of the coroutine_start function is to create a coroutine by calling the factory function and launching it for execution. The first call to the coroutine send method, must be with the parameter None - this starts the coroutine
  5. The hello function itself is trivial. Maybe that's understandable, but I think it is worth clarifying. This feature is not coroutine! This function, which creates and returns a coroutine (factory function), is similar to the functions that create and return a generator.


The development of this idea: “async / await coroutine and event-driven”, can be viewed at this link . It is still raw, but in addition to the shown switching on the timeout event, the coroutines switching on the I / O ready and system sygnal events is implemented. As a demo, there is an example of an asynchronous echo server.

Finally


Coroutines in one form or another have been available for quite some time, but there were no “official rules of the game with them.” Now these “rules of the game” are defined and fixed, and this will be a good reason to use asynchronous programming methods more widely in Python.

Source: https://habr.com/ru/post/266743/


All Articles