8000 bpo-34831: Asyncio tutorial by cjrh · Pull Request #9748 · python/cpython · GitHub
[go: up one dir, main page]

Skip to content

bpo-34831: Asyncio tutorial #9748

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 31 commits into from
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
16d3b94
Create basic structure of the asyncio tutorial
cjrh Oct 7, 2018
50a901e
Begun work on the case study for the server
cjrh Oct 14, 2018
dfede40
Incorporate review comments from @willingc
cjrh Oct 21, 2018
a11e659
Refine language around threads and processes
cjrh Oct 21, 2018
7e205d2
Incorporate message handling into server code
cjrh Oct 21, 2018
7f2f149
Add message receiving to server code.
cjrh Oct 21, 2018
61402e1
Added skeleton suggestions for the cookbook section
cjrh Oct 21, 2018
550bdbf
Further notes in the cookbook
cjrh Oct 21, 2018
e7bc56d
Further work on describing how async def functions work
cjrh Nov 4, 2018
3d4cdae
Fix review comment from @tirkarthi
cjrh Jun 15, 2019
e0bb48b
Fix typo
cjrh Jun 15, 2019
5e4550a
Clarify the "What is async" section
cjrh Jun 15, 2019
0de2748
Flesh out the sync-versus-async functions section
cjrh Jun 15, 2019
89364f8
Add the blurb entry
cjrh Jun 15, 2019
be474f4
Remove TODOs
cjrh Jun 15, 2019
c403101
Write "Executing Async Functions"
cjrh Jun 15, 2019
69190b8
Fix spurious backtick
cjrh Jun 15, 2019
89f7ca2
Make the case study (server) a little neater.
cjrh Jun 15, 2019
36fc743
Some refactoring and finishing off the server.
cjrh Jun 15, 2019
d55d8fb
Cleaned up the last bit of the chat server code sample.
cjrh Jun 16, 2019
34306f0
Further progress - got a CLI chat client working using prompt-toolkit.
cjrh Jun 16, 2019
0c82755
Include chat client code in the text.
cjrh Jun 16, 2019
a774a98
Fix typo
cjrh Jun 17, 2019
eedbc97
Clarify switching behaviour
cjrh Jun 17, 2019
a8a801d
Add async generators and async context managers discussion.
cjrh Jun 17, 2019
8e6dcfd
Add some comparison with JavaScript async/await and asyncio.create_task
cjrh Jun 17, 2019
0e5ed3f
Fix "no good read" typo
cjrh Jun 17, 2019
4714ed2
Fix "do not required" typo
cjrh Jun 17, 2019
d71da67
Modern -> modern
cjrh Jun 17, 2019
26cc634
Removing the GUI case study section
cjrh Jun 19, 2019
9530021
Remove problematic backticks inside a code-block
cjrh Sep 11, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Further work on describing how async def functions work
  • Loading branch information
cjrh committed Sep 11, 2019
commit e7bc56d4b52f1ded7976bc1b012212e6ef8f34ff
271 changes: 267 additions & 4 deletions Doc/library/asyncio-tutorial/async-functions.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ Functions: Sync vs Async
Regular Python functions are created with the keyword ``def``,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Regular Python functions are created with the keyword ``def``,
Regular Python functions are created with the keyword :keyword:`def`,

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pushing :keyword: everywhere for building a link doesn't make sense but the first occurrence in a page can be a link, it's convenient maybe.

and look like this:

.. code-block:: python
.. code-block:: python3

def f(x, y):
print(x + y)
Expand All @@ -14,7 +14,7 @@ and look like this:

Async functions are different in two respects:

.. code-block:: python
.. code-block:: python3

import asyncio

Expand All @@ -39,7 +39,7 @@ way.

That means the following:

.. code-block:: python
.. code-block:: python3

import asyncio

Expand All @@ -52,10 +52,273 @@ That means the following:
asyncio.run(main())


The execution of the first async function, ``main()``, is performed
The execution of the the async function ``main()`` is performed
with ``run()``, but once you're inside an ``async def`` function, then
all you need to execute another async function is the ``await`` keyword.

Ordinary (Sync) Functions Cannot Await Async Functions
------------------------------------------------------

If you're starting inside a normal function, you cannot call an
async function using the ``await`` keyword. Doing so will produce
a syntax error:

.. code-block:: python3

>>> async def f(x, y):
... pass
...
>>> def g():
... await f(1, 2)
...
File "<input>", line 2
SyntaxError: 'await' outside async function

To fix this, either ``asyncio.run()`` must be used, or the function
``g`` must be changed to be an ``async def`` function. The ``run()``
function is tricky: you can't call it in a nested way, because
internally it creates an event loop and you cannot have two event
loops running at the same time (in the same thread). So the following
is also illegal:

.. code-block:: python3

>>> import asyncio
>>> async def f(x, y):
... pass
...
>>> def g():
... asyncio.run(f(1, 2))
...
>>> async def main():
... g()
...
>>> asyncio.run(main())
Traceback (most recent call last):

<snip>

File "G:\Programs\Python37\lib\asyncio\runners.py", line 34, in run
"asyncio.run() cannot be called from a running event loop")
RuntimeError: asyncio.run() cannot be called from a running event loop

So ``asyncio.run()`` is really intended only for launching your *first*
async function; after that, every other async function should be
executed using the ``await`` keyword, and the task-based methods which
we've not yet discussed.

Async Functions Can Call Sync Functions
---------------------------------------

The inverse works perfectly fine: calling ordinary Python functions
from inside ``async def`` functions. Here's an example:

.. code-block:: python3

>>> import asyncio
>>> import time
>>> async def f():
... print(time.ctime())
...
>>> asyncio.run(f())
Sun Nov 4 15:04:45 2018

Accurate Terminology For Async Functions
----------------------------------------

So far in this tutorial we've been intentionally sloppy with how
we refer to things like *async functions* or *async def* functions,
and *normal Python functions* and so on. It's time to get more
specific about what to call each of these things. It's important
because we need to be able to understand the difference between
a **coroutine** and a **coroutine function**, and a few other things
still to be introduced.

So let's do that now, using the ``inspect`` module. First let's look
at the two kinds of functions:

.. code-block:: python3

>>> import inspect
>>> def f1():
... pass
...
>>> inspect.isfunction(f1)
True
>>> inspect.iscoroutinefunction(f1)
False

This is an ordinary Python function, and the ``inspect`` module
confirms that, but we've included another test to see if the function
is a *coroutine function*, which is ``False`` as expected. Let's do
the same on an ``async def`` function:

.. code-block:: python3

>>> async def f2():
... pass
...
>>> inspect.isfunction(f2)
True
>>> inspect.iscoroutinefunction(f2)
True

According to Python, ``f2`` is also considered to be a function, but
more specifically, it is a *coroutine function*, and this is the
specific name we will be using for *async def* functions.

Why does it matter? Well, when you evaluate a coroutine function, it'll
return something:

.. code-block:: python3

>>> async def f2():
... pass
...
>>> result = f2()
>>> type(result)
<class 'coroutine'>
>>> inspect.iscoroutine(result)
True

The point we're trying to make here is that an *async def* function
is not yet a coroutine, but rather only a *coroutine function*; only
when you *evaluate* the coroutine function, will a coroutine
object be returned. The ``await`` keyword, which we showed in
previous examples, is acting on *coroutine* objects, not
the coroutine functions that create them.

This can be made clear in the following example:

.. code-block:: python3

>>> async def f3():
... return 123
...
>>> async def main():
... obj = f3()
... result = await obj
... print(result)
...
>>> asyncio.run(main())
123

In the code above, the value of ``obj`` is *not* ``123`` when
coroutine function ``f3`` is evaluated. Instead, ``obj`` is a
*coroutine* object, and it will only get executed when the
``await`` keyword is used. Of course, you don't have to write
code like this where you first get the coroutine and then
use ``await`` on the object; simply evaluate the
coroutine function and use ``await`` all in the same line.

An Aside: Similarity To Generator Functions
-------------------------------------------

This has nothing to do with asyncio, but you might be interested
to see how this difference between a function and a
coroutine function is quite similar to the difference between
functions and generator functions:

.. code-block:: python3

>>> def g():
... yield 123
...
>>> inspect.isfunction(g)
True
>>> inspect.isgeneratorfunction(g)
True

If a function uses the ``yield`` keyword anywhere inside the function
body, that function becomes a *generator function*, very similar to
how a function declared with ``async def`` becomes a
*coroutine function*. And, completing the comparison, if you
evaluate a generator function, a *generator* object is returned, similar
to how a coroutine function, when evaluated, returns a coroutine
object:

.. code-block:: python3
6D4E
>>> def g():
... yield 123
...
>>> obj = g()
>>> type(obj)
<class 'generator'>
>>> inspect.isgenerator(obj)
True

Again, this doesn't have anything to do with asyncio, but
the loose similarity between generator functions and
coroutine functions might give you a useful framework for understanding
the new coroutine functions.

Terminology For Async Generators
--------------------------------

The previous section was useful for giving you a basic framework
for understanding how coroutines and generator have similar
characteristics. Here, we show how we can also make asynchronous
generator functions! It sounds much more complicated than it
really is, so let's jump directly to some examples:

.. code-block:: python3

>>> import asyncio
>>> async def ag():
... yield 123
...
>>> async def main():
... async for value in ag():
... print(value)
...
>>> asyncio.run(main())
123

If you pretend for a second that the word "async" is temporarily
removed from the code above, the behaviour of the generator
should look very familiar to you (assuming you already know how
Python's generators work). The generator function yields out
values and these values are obtained by iterating over the
generator.

The difference now is of course the presence of those "async"
words. The code sample doesn't a good reason *why* an async
generator is being used here. That will come later in the
cookbook. All we want to discuss here is what these kinds of
functions and objects should be called.

Let's have a close look at the function `ag`:

.. code-block:: python3

>>> async def ag():
... yield 123
...
>>> inspect.isfunction(ag)
True

# Ok, so it's a function...

>>> inspect.iscoroutinefunction(ag)
False

# ...but it's not a coroutine function, despite "async def"

>>> inspect.isasyncgenfunction(ag)
True

# Aha, so this is an "async generator function"...

>>> inspect.isasyncgen(ag())
True

# ...and when evaluated, it returns an "async generator"





TODO:
- which kind of functions can be called from which other kind
- use the "inspect" module to verify the formal names of functions,
Expand Down
13 changes: 13 additions & 0 deletions Doc/library/asyncio-tutorial/asyncio-cookbook.rst
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,8 @@ Correctly Closing Connections

- from the client side
- from the server side
- Yury I need your help here. What is the actual "correct" way
to do this? Streams API preferable, if possible.

Handling Typical Socket Errors
------------------------------
Expand Down Expand Up @@ -236,6 +238,17 @@ Run A Blocking Call In An Executor
- show example with a custom executor (process-based)


Adding Asyncio To An Existing Sync (Threaded) Application
---------------------------------------------------------

- Imagine an existing app that uses threading for concurrency,
but we want to make use of asyncio only for, say, a large
number of concurrent GET requests, but leave the rest of the
app unchanged.
- Plan would be to run the asyncio loop in another thread
- Can show how to safely communicate between that thread and
the main thread (or others).



Notes:
Expand Down
4 changes: 2 additions & 2 deletions Doc/library/asyncio-tutorial/what-asyncio.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ What Does "Async" Mean?

Let's make a function that communicates over the network:

.. code-block:: python
.. code-block:: python3

import socket

Expand All @@ -30,7 +30,7 @@ Python only ever executes one line at a time.
Now the question comes up: what if you need to send a greeting to
*multiple* hosts? You could just call it twice, right?

.. code-block:: python
.. code-block:: python3

import socket

Expand Down
0