Skip to content

Commit 054e9c8

Browse files
bpo-33346: Allow async comprehensions inside implicit async comprehensions (GH-6766)
Co-authored-by: Pablo Galindo <[email protected]>
1 parent 0ee0a74 commit 054e9c8

File tree

6 files changed

+109
-17
lines changed

6 files changed

+109
-17
lines changed

Doc/reference/expressions.rst

+8-3
Original file line numberDiff line numberDiff line change
@@ -218,9 +218,9 @@ A comprehension in an :keyword:`!async def` function may consist of either a
218218
:keyword:`!for` or :keyword:`!async for` clause following the leading
219219
expression, may contain additional :keyword:`!for` or :keyword:`!async for`
220220
clauses, and may also use :keyword:`await` expressions.
221-
If a comprehension contains either :keyword:`!async for` clauses
222-
or :keyword:`!await` expressions it is called an
223-
:dfn:`asynchronous comprehension`. An asynchronous comprehension may
221+
If a comprehension contains either :keyword:`!async for` clauses or
222+
:keyword:`!await` expressions or other asynchronous comprehensions it is called
223+
an :dfn:`asynchronous comprehension`. An asynchronous comprehension may
224224
suspend the execution of the coroutine function in which it appears.
225225
See also :pep:`530`.
226226

@@ -230,6 +230,11 @@ See also :pep:`530`.
230230
.. versionchanged:: 3.8
231231
``yield`` and ``yield from`` prohibited in the implicitly nested scope.
232232

233+
.. versionchanged:: 3.11
234+
Asynchronous comprehensions are now allowed inside comprehensions in
235+
asynchronous functions. Outer comprehensions implicitly become
236+
asynchronous.
237+
233238

234239
.. _lists:
235240

Doc/whatsnew/3.11.rst

+10-7
Original file line numberDiff line numberDiff line change
@@ -148,16 +148,19 @@ See :pep:`657` for more details. (Contributed by Pablo Galindo, Batuhan Taskaya
148148
and Ammar Askar in :issue:`43950`.)
149149

150150

151-
152151
Other Language Changes
153152
======================
154153

155-
A :exc:`TypeError` is now raised instead of an :exc:`AttributeError` in
156-
:meth:`contextlib.ExitStack.enter_context` and
157-
:meth:`contextlib.AsyncExitStack.enter_async_context` for objects which do not
158-
support the :term:`context manager` or :term:`asynchronous context manager`
159-
protocols correspondingly.
160-
(Contributed by Serhiy Storchaka in :issue:`44471`.)
154+
* Asynchronous comprehensions are now allowed inside comprehensions in
155+
asynchronous functions. Outer comprehensions implicitly become
156+
asynchronous. (Contributed by Serhiy Storchaka in :issue:`33346`.)
157+
158+
* A :exc:`TypeError` is now raised instead of an :exc:`AttributeError` in
159+
:meth:`contextlib.ExitStack.enter_context` and
160+
:meth:`contextlib.AsyncExitStack.enter_async_context` for objects which do not
161+
support the :term:`context manager` or :term:`asynchronous context manager`
162+
protocols correspondingly.
163+
(Contributed by Serhiy Storchaka in :issue:`44471`.)
161164

162165
* A :exc:`TypeError` is now raised instead of an :exc:`AttributeError` in
163166
:keyword:`with` and :keyword:`async with` statements for objects which do not

Lib/test/test_coroutines.py

+72
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,12 @@ def __await__(self):
2828
yield self.value
2929

3030

31+
async def asynciter(iterable):
32+
"""Convert an iterable to an asynchronous iterator."""
33+
for x in iterable:
34+
yield x
35+
36+
3137
def run_async(coro):
3238
assert coro.__class__ in {types.GeneratorType, types.CoroutineType}
3339

@@ -125,6 +131,11 @@ def bar():
125131
for c in b]
126132
""",
127133

134+
"""async def foo():
135+
def bar():
136+
[[async for i in b] for b in els]
137+
""",
138+
128139
"""async def foo():
129140
def bar():
130141
[i for i in els
@@ -200,6 +211,13 @@ def bar():
200211
[i for i in els if await i]
201212
""",
202213

214+
"""def bar():
215+
[[i async for i in a] for a in elts]
216+
""",
217+
218+
"""[[i async for i in a] for a in elts]
219+
""",
220+
203221
"""async def foo():
204222
await
205223
""",
@@ -2011,6 +2029,60 @@ async def f():
20112029
run_async(f()),
20122030
([], {1: 1, 2: 2, 3: 3}))
20132031

2032+
def test_nested_comp(self):
2033+
async def run_list_inside_list():
2034+
return [[i + j async for i in asynciter([1, 2])] for j in [10, 20]]
2035+
self.assertEqual(
2036+
run_async(run_list_inside_list()),
2037+
([], [[11, 12], [21, 22]]))
2038+
2039+
async def run_set_inside_list():
2040+
return [{i + j async for i in asynciter([1, 2])} for j in [10, 20]]
2041+
self.assertEqual(
2042+
run_async(run_set_inside_list()),
2043+
([], [{11, 12}, {21, 22}]))
2044+
2045+
async def run_list_inside_set():
2046+
return {sum([i async for i in asynciter(range(j))]) for j in [3, 5]}
2047+
self.assertEqual(
2048+
run_async(run_list_inside_set()),
2049+
([], {3, 10}))
2050+
2051+
async def run_dict_inside_dict():
2052+
return {j: {i: i + j async for i in asynciter([1, 2])} for j in [10, 20]}
2053+
self.assertEqual(
2054+
run_async(run_dict_inside_dict()),
2055+
([], {10: {1: 11, 2: 12}, 20: {1: 21, 2: 22}}))
2056+
2057+
async def run_list_inside_gen():
2058+
gen = ([i + j async for i in asynciter([1, 2])] for j in [10, 20])
2059+
return [x async for x in gen]
2060+
self.assertEqual(
2061+
run_async(run_list_inside_gen()),
2062+
([], [[11, 12], [21, 22]]))
2063+
2064+
async def run_gen_inside_list():
2065+
gens = [(i async for i in asynciter(range(j))) for j in [3, 5]]
2066+
return [x for g in gens async for x in g]
2067+
self.assertEqual(
2068+
run_async(run_gen_inside_list()),
2069+
([], [0, 1, 2, 0, 1, 2, 3, 4]))
2070+
2071+
async def run_gen_inside_gen():
2072+
gens = ((i async for i in asynciter(range(j))) for j in [3, 5])
2073+
return [x for g in gens async for x in g]
2074+
self.assertEqual(
2075+
run_async(run_gen_inside_gen()),
2076+
([], [0, 1, 2, 0, 1, 2, 3, 4]))
2077+
2078+
async def run_list_inside_list_inside_list():
2079+
return [[[i + j + k async for i in asynciter([1, 2])]
2080+
for j in [10, 20]]
2081+
for k in [100, 200]]
2082+
self.assertEqual(
2083+
run_async(run_list_inside_list_inside_list()),
2084+
([], [[[111, 112], [121, 122]], [[211, 212], [221, 222]]]))
2085+
20142086
def test_copy(self):
20152087
async def func(): pass
20162088
coro = func()
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Asynchronous comprehensions are now allowed inside comprehensions in
2+
asynchronous functions. Outer comprehensions implicitly become
3+
asynchronous.

Python/compile.c

+8-6
Original file line numberDiff line numberDiff line change
@@ -4947,11 +4947,9 @@ compiler_comprehension(struct compiler *c, expr_ty e, int type,
49474947
PyCodeObject *co = NULL;
49484948
comprehension_ty outermost;
49494949
PyObject *qualname = NULL;
4950+
int scope_type = c->u->u_scope_type;
49504951
int is_async_generator = 0;
4951-
int top_level_await = IS_TOP_LEVEL_AWAIT(c);
4952-
4953-
4954-
int is_async_function = c->u->u_ste->ste_coroutine;
4952+
int is_top_level_await = IS_TOP_LEVEL_AWAIT(c);
49554953

49564954
outermost = (comprehension_ty) asdl_seq_GET(generators, 0);
49574955
if (!compiler_enter_scope(c, name, COMPILER_SCOPE_COMPREHENSION,
@@ -4963,7 +4961,11 @@ compiler_comprehension(struct compiler *c, expr_ty e, int type,
49634961

49644962
is_async_generator = c->u->u_ste->ste_coroutine;
49654963

4966-
if (is_async_generator && !is_async_function && type != COMP_GENEXP && !top_level_await) {
4964+
if (is_async_generator && type != COMP_GENEXP &&
4965+
scope_type != COMPILER_SCOPE_ASYNC_FUNCTION &&
4966+
scope_type != COMPILER_SCOPE_COMPREHENSION &&
4967+
!is_top_level_await)
4968+
{
49674969
compiler_error(c, "asynchronous comprehension outside of "
49684970
"an asynchronous function");
49694971
goto error_in_scope;
@@ -5002,7 +5004,7 @@ compiler_comprehension(struct compiler *c, expr_ty e, int type,
50025004
qualname = c->u->u_qualname;
50035005
Py_INCREF(qualname);
50045006
compiler_exit_scope(c);
5005-
if (top_level_await && is_async_generator){
5007+
if (is_top_level_await && is_async_generator){
50065008
c->u->u_ste->ste_coroutine = 1;
50075009
}
50085010
if (co == NULL)

Python/symtable.c

+8-1
Original file line numberDiff line numberDiff line change
@@ -2056,7 +2056,14 @@ symtable_handle_comprehension(struct symtable *st, expr_ty e,
20562056
return 0;
20572057
}
20582058
st->st_cur->ste_generator = is_generator;
2059-
return symtable_exit_block(st);
2059+
int is_async = st->st_cur->ste_coroutine && !is_generator;
2060+
if (!symtable_exit_block(st)) {
2061+
return 0;
2062+
}
2063+
if (is_async) {
2064+
st->st_cur->ste_coroutine = 1;
2065+
}
2066+
return 1;
20602067
}
20612068

20622069
static int

0 commit comments

Comments
 (0)