##// END OF EJS Templates
fix docs
Matthias Bussonnier -
Show More
@@ -1,186 +1,185 b''
1
2 .. autoawait:
1 .. _autoawait:
3 2
4 3 Asynchronous in REPL: Autoawait
5 4 ===============================
6 5
7 6 Starting with IPython 6.0, and when user Python 3.6 and above, IPython offer the
8 7 ability to run asynchronous code from the REPL. constructs which are
9 8 :exc:`SyntaxError` s in the Python REPL can be used seamlessly in IPython.
10 9
11 10 When a supported libray is used, IPython will automatically `await` Futures
12 11 and Coroutines in the REPL. This will happen if an :ref:`await <await>` (or `async`) is
13 12 use at top level scope, or if any structure valid only in `async def
14 13 <https://docs.python.org/3/reference/compound_stmts.html#async-def>`_ function
15 14 context are present. For example, the following being a syntax error in the
16 15 Python REPL::
17 16
18 17 Python 3.6.0
19 18 [GCC 4.2.1]
20 19 Type "help", "copyright", "credits" or "license" for more information.
21 20 >>> import aiohttp
22 21 >>> result = aiohttp.get('https://api.github.com')
23 22 >>> response = await result
24 23 File "<stdin>", line 1
25 24 response = await result
26 25 ^
27 26 SyntaxError: invalid syntax
28 27
29 28 Should behave as expected in the IPython REPL::
30 29
31 30 Python 3.6.0
32 31 Type 'copyright', 'credits' or 'license' for more information
33 32 IPython 6.0.0.dev -- An enhanced Interactive Python. Type '?' for help.
34 33
35 34 In [1]: import aiohttp
36 35 ...: result = aiohttp.get('https://api.github.com')
37 36
38 37 In [2]: response = await result
39 38 <pause for a few 100s ms>
40 39
41 40 In [3]: await response.json()
42 41 Out[3]:
43 42 {'authorizations_url': 'https://api.github.com/authorizations',
44 43 'code_search_url': 'https://api.github.com/search/code?q={query}...',
45 44 ...
46 45 }
47 46
48 47
49 48 You can use the ``c.InteractiveShell.autoawait`` configuration option and set it
50 49 to :any:`False` to deactivate automatic wrapping of asynchronous code. You can also
51 50 use the :magic:`%autoawait` magic to toggle the behavior at runtime::
52 51
53 52 In [1]: %autoawait False
54 53
55 54 In [2]: %autoawait
56 55 IPython autoawait is `Off`, and set to use `IPython.core.interactiveshell._asyncio_runner`
57 56
58 57
59 58
60 59 By default IPython will assume integration with Python's provided
61 60 :mod:`asyncio`, but integration with other libraries is provided. In particular
62 61 we provide experimental integration with the ``curio`` and ``trio`` library.
63 62
64 63 You can switch current integration by using the
65 64 ``c.InteractiveShell.loop_runner`` option or the ``autoawait <name
66 65 integration>`` magic.
67 66
68 67 For example::
69 68
70 69 In [1]: %autoawait trio
71 70
72 71 In [2]: import trio
73 72
74 73 In [3]: async def child(i):
75 74 ...: print(" child %s goes to sleep"%i)
76 75 ...: await trio.sleep(2)
77 76 ...: print(" child %s wakes up"%i)
78 77
79 78 In [4]: print('parent start')
80 79 ...: async with trio.open_nursery() as n:
81 80 ...: for i in range(5):
82 81 ...: n.spawn(child, i)
83 82 ...: print('parent end')
84 83 parent start
85 84 child 2 goes to sleep
86 85 child 0 goes to sleep
87 86 child 3 goes to sleep
88 87 child 1 goes to sleep
89 88 child 4 goes to sleep
90 89 <about 2 seconds pause>
91 90 child 2 wakes up
92 91 child 1 wakes up
93 92 child 0 wakes up
94 93 child 3 wakes up
95 94 child 4 wakes up
96 95 parent end
97 96
98 97
99 98 In the above example, ``async with`` at top level scope is a syntax error in
100 99 Python.
101 100
102 101 Using this mode can have unexpected consequences if used in interaction with
103 102 other features of IPython and various registered extensions. In particular if you
104 103 are a direct or indirect user of the AST transformers, these may not apply to
105 104 your code.
106 105
107 106 The default loop, or runner does not run in the background, so top level
108 107 asynchronous code must finish for the REPL to allow you to enter more code. As
109 108 with usual Python semantic, the awaitables are started only when awaited for the
110 109 first time. That is to say, in first example, no network request is done between
111 110 ``In[1]`` and ``In[2]``.
112 111
113 112
114 113 Internals
115 114 =========
116 115
117 116 As running asynchronous code is not supported in interactive REPL as of Python
118 117 3.6 we have to rely to a number of complex workaround to allow this to happen.
119 118 It is interesting to understand how this works in order to understand potential
120 119 bugs, or provide a custom runner.
121 120
122 121 Among the many approaches that are at our disposition, we find only one that
123 122 suited out need. Under the hood we :ct the code object from a async-def function
124 123 and run it in global namesapace after modifying the ``__code__`` object.::
125 124
126 125 async def inner_async():
127 126 locals().update(**global_namespace)
128 127 #
129 128 # here is user code
130 129 #
131 130 return last_user_statement
132 131 codeobj = modify(inner_async.__code__)
133 132 coroutine = eval(codeobj, user_ns)
134 133 display(loop_runner(coroutine))
135 134
136 135
137 136
138 137 The first thing you'll notice is that unlike classical ``exec``, there is only
139 138 one name space. Second, user code runs in a function scope, and not a module
140 139 scope.
141 140
142 141 On top of the above there are significant modification to the AST of
143 142 ``function``, and ``loop_runner`` can be arbitrary complex. So there is a
144 143 significant overhead to this kind of code.
145 144
146 145 By default the generated coroutine function will be consumed by Asyncio's
147 146 ``loop_runner = asyncio.get_evenloop().run_until_complete()`` method. It is
148 147 though possible to provide your own.
149 148
150 149 A loop runner is a *synchronous* function responsible from running a coroutine
151 150 object.
152 151
153 152 The runner is responsible from ensuring that ``coroutine`` run to completion,
154 153 and should return the result of executing the coroutine. Let's write a
155 154 runner for ``trio`` that print a message when used as an exercise, ``trio`` is
156 155 special as it usually prefer to run a function object and make a coroutine by
157 156 itself, we can get around this limitation by wrapping it in an async-def without
158 157 parameters and passing this value to ``trio``::
159 158
160 159
161 160 In [1]: import trio
162 161 ...: from types import CoroutineType
163 162 ...:
164 163 ...: def trio_runner(coro:CoroutineType):
165 164 ...: print('running asynchronous code')
166 165 ...: async def corowrap(coro):
167 166 ...: return await coro
168 167 ...: return trio.run(corowrap, coro)
169 168
170 169 We can set it up by passing it to ``%autoawait``::
171 170
172 171 In [2]: %autoawait trio_runner
173 172
174 173 In [3]: async def async_hello(name):
175 174 ...: await trio.sleep(1)
176 175 ...: print(f'Hello {name} world !')
177 176 ...: await trio.sleep(1)
178 177
179 178 In [4]: await async_hello('async')
180 179 running asynchronous code
181 180 Hello async world !
182 181
183 182
184 183 Asynchronous programming in python (and in particular in the REPL) is still a
185 184 relatively young subject. Feel free to contribute improvements to this codebase
186 185 and give us feedback.
General Comments 0
You need to be logged in to leave comments. Login now