https://github.com/python/cpython/commit/42de93a62e711b695439c818a6a7f41eb2761c45
commit: 42de93a62e711b695439c818a6a7f41eb2761c45
branch: main
author: Irit Katriel <[email protected]>
committer: iritkatriel <[email protected]>
date: 2026-09-01T18:49:05+01:00
summary:
Add tests for async comprehensions in different scopes (#156412)
files:
M Lib/test/test_builtin.py
M Lib/test/test_coroutines.py
diff --git a/Lib/test/test_builtin.py b/Lib/test/test_builtin.py
index 1d2c105ac047e1..294355698c8683 100644
--- a/Lib/test/test_builtin.py
+++ b/Lib/test/test_builtin.py
@@ -542,6 +542,10 @@ async def sleep(delay, result=None):
'''a = [x async for x in (x async for x in arange(5))][1]''',
'''a, = [1 for x in {x async for x in arange(1)}]''',
'''a = [await sleep(0, x) async for x in arange(2)][1]''',
+ '''a = [await sleep(0, 1) for _ in [0]][0]''',
+ '''a = {await sleep(0, 1) for _ in [0]}.pop()''',
+ '''a = {0: await sleep(0, 1) for _ in [0]}[0]''',
+ '''a = (lambda x=[await sleep(0, 1) for _ in [0]]: x)()[0]''',
# gh-121637: Make sure we correctly handle the case where the
# async code is optimized away
'''assert not await sleep(0); a = 1''',
@@ -610,7 +614,26 @@ async def __aexit__(self, *exc_info):
'''def f():
async with Lock() as l:
a = 1
- '''
+ ''',
+ '''class C:
+ [await x for x in y]
+ ''',
+ '''class C:
+ [x async for x in arange(10)]
+ ''',
+ '''async def f():
+ class C:
+ [await x for x in y]
+ ''',
+ '''lambda: [await x for x in y]''',
+ '''class C:
+ def f(self, x=[await y for y in z]):
+ pass
+ ''',
+ '''type T = [await x for x in y]''',
+ '''async def f[T=[await x for x in y]]():
+ pass
+ ''',
]
for mode, code_sample in product(modes, code_samples):
source = dedent(code_sample)
diff --git a/Lib/test/test_coroutines.py b/Lib/test/test_coroutines.py
index 9d415238876c8f..ab854d56d5a3eb 100644
--- a/Lib/test/test_coroutines.py
+++ b/Lib/test/test_coroutines.py
@@ -1,3 +1,4 @@
+import ast
import contextlib
import copy
import inspect
@@ -407,6 +408,118 @@ async def bar():
with self.subTest(code=code), self.assertRaises(SyntaxError):
compile(code, "<test>", "exec")
+ def test_async_comprehension_scope(self):
+ # List/set/dict comprehensions with await or async for are allowed
+ # only in async functions, or at module level with top-level await.
+ allowed = [
+ "async def f():\n [await x for x in y]",
+ "async def f():\n {await x for x in y}",
+ "async def f():\n {k: await x for k, x in y}",
+ "async def f():\n [x async for x in y]",
+ "async def f():\n {x async for x in y}",
+ "async def f():\n {k: x async for k, x in y}",
+ "async def f():\n [[await x for x in y] for y in z]",
+ # Defaults, bases, and genexp iterables are evaluated in the
+ # enclosing scope.
+ "async def outer():\n async def f(x=[await y for y in z]):
pass",
+ "async def f():\n class C([await x for x in y]): pass",
+ "async def f():\n (x for x in [await y for y in z])",
+ "async def f():\n (x for x in [y async for y in z])",
+ ]
+ for code in allowed:
+ with self.subTest(code=code):
+ compile(code, "<test>", "exec")
+
+ # Generator expressions with await are async genexps and may appear
+ # outside async functions. A listcomp nested in a genexp body is also
+ # allowed (the genexp becomes an async generator).
+ for code in [
+ "(await x for x in y)",
+ "def f():\n (await x for x in y)",
+ "class C:\n (await x for x in y)",
+ "lambda: (await x for x in y)",
+ "([await x for x in y] for y in z)",
+ "def f():\n ([await x for x in y] for y in z)",
+ "class C:\n ([await x for x in y] for y in z)",
+ "async def f():\n ([await x for x in y] for y in z)",
+ ]:
+ with self.subTest(code=code):
+ compile(code, "<test>", "exec")
+
+ err = "asynchronous comprehension outside of an asynchronous function"
+ invalid = [
+ "[await x for x in y]",
+ "{await x for x in y}",
+ "{k: await x for k, x in y}",
+ "[x async for x in y]",
+ "{x async for x in y}",
+ "{k: x async for k, x in y}",
+ "[[await x for x in y] for y in z]",
+ "[[x async for x in y] for y in z]",
+ "def f():\n [await x for x in y]",
+ "def f():\n [x async for x in y]",
+ "async def f():\n def g():\n [await x for x in y]",
+ "class C:\n [await x for x in y]",
+ "class C:\n {await x for x in y}",
+ "class C:\n {k: await x for k, x in y}",
+ "class C:\n [x async for x in y]",
+ "class C:\n [[await x for x in y] for y in z]",
+ "async def f():\n class C:\n x = [await y for y in z]",
+ "async def f():\n class C:\n x = [y async for y in z]",
+ # Lambdas are never async, even inside async def.
+ "lambda: [await x for x in y]",
+ "async def f():\n lambda: [await x for x in y]",
+ "class C:\n f = lambda: [await x for x in y]",
+ # Defaults, bases, and genexp iterables run in the enclosing scope.
+ "(x for x in [await y for y in z])",
+ "(x for x in [y async for y in z])",
+ "def f():\n (x for x in [await y for y in z])",
+ "async def f(x=[await y for y in z]): pass",
+ "def f(x=[await y for y in z]): pass",
+ "class C:\n def f(self, x=[await y for y in z]): pass",
+ "class C([await x for x in y]): pass",
+ # Type aliases and type-parameter scopes.
+ "type T = [await x for x in y]",
+ "type T = [x async for x in y]",
+ "def f[T=[await x for x in y]](): pass",
+ "def f[T: [await x for x in y]](): pass",
+ "async def f[T=[await x for x in y]](): pass",
+ "async def f(x: [await y for y in z]): pass",
+ ]
+ for code in invalid:
+ with self.subTest(code=code):
+ support.check_syntax_error(self, code, err)
+
+ support.check_syntax_error(
+ self, "await x", "'await' outside function")
+ support.check_syntax_error(
+ self, "class C:\n await x", "'await' outside function")
+ support.check_syntax_error(
+ self, "def f():\n await x", "'await' outside async function")
+
+ flags = ast.PyCF_ALLOW_TOP_LEVEL_AWAIT
+ for code in [
+ "[await x for x in y]",
+ "async def f(x=[await y for y in z]): pass",
+ "class C([await x for x in y]): pass",
+ "f'{[await x for x in y]}'",
+ "(x for x in [await y for y in z])",
+ "(x for x in [y async for y in z])",
+ ]:
+ with self.subTest(code=code, tla=True):
+ compile(code, "<test>", "exec", flags=flags)
+ still_invalid = [
+ "lambda: [await x for x in y]",
+ "def f():\n (x for x in [await y for y in z])",
+ "class C:\n def f(self, x=[await y for y in z]): pass",
+ "type T = [await x for x in y]",
+ "async def f[T=[await x for x in y]](): pass",
+ ]
+ for code in still_invalid:
+ with self.subTest(code=code, tla=True):
+ with self.assertRaisesRegex(SyntaxError, err):
+ compile(code, "<test>", "exec", flags=flags)
+
def test_badsyntax_2(self):
samples = [
"""def foo():
_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]