From 2721f8a1a3c51d0b157ad114091449494e93e200 Mon Sep 17 00:00:00 2001 From: tangyuan0821 Date: Sun, 24 Aug 2025 14:56:39 +0800 Subject: [PATCH 1/5] Doc: Clarify async generator expression behavior with nested async comprehensions --- Doc/reference/expressions.rst | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/Doc/reference/expressions.rst b/Doc/reference/expressions.rst index 9aca25e3214a16..7aa60cd7703bae 100644 --- a/Doc/reference/expressions.rst +++ b/Doc/reference/expressions.rst @@ -310,6 +310,11 @@ the iterable expression in the leftmost :keyword:`!for` clause, it is called an execution of the coroutine function in which it appears. See also :pep:`530`. +Note that this behavior extends to nested comprehensions: if a comprehension +contains an asynchronous comprehension in a nested scope, the outer comprehension +implicitly becomes asynchronous. This also applies to generator expressions that +contain asynchronous comprehensions. + .. versionadded:: 3.6 Asynchronous comprehensions were introduced. @@ -484,6 +489,17 @@ clauses or :keyword:`await` expressions it is called an expression returns a new asynchronous generator object, which is an asynchronous iterator (see :ref:`async-iterators`). +Note that a generator expression becomes an asynchronous generator expression +if it contains asynchronous comprehensions in nested scopes, such as an async +list comprehension within the generator expression. For example:: + + # This becomes an async generator expression + ([a async for a in async_iterable] for x in [1]) + +This behavior occurs because the inner async comprehension requires an +asynchronous context to execute, which causes the entire generator expression +to become asynchronous. + .. versionadded:: 3.6 Asynchronous generator expressions were introduced. From 6170e3c68f649d254c6cec375f6b55805410d674 Mon Sep 17 00:00:00 2001 From: tangyuan0821 Date: Sun, 24 Aug 2025 15:11:56 +0800 Subject: [PATCH 2/5] Doc: Clarify async generator expression behavior with nested async comprehensions --- .gitignore | 13 +++++++++++++ Doc/reference/expressions.rst | 25 +++++++++++++++---------- 2 files changed, 28 insertions(+), 10 deletions(-) diff --git a/.gitignore b/.gitignore index e842676d866bf8..286f86dc25ca6e 100644 --- a/.gitignore +++ b/.gitignore @@ -178,3 +178,16 @@ CLAUDE.local.md #### main branch only stuff below this line, things to backport go above. #### # main branch only: ABI files are not checked/maintained. Doc/data/python*.abi +validation_test.py +test_nested_difference.py +commit_message.txt +demo_issue_138097.py +PR_template.md +check_format.py +lint_check.py +Doc/reference/expressions.rst +check_our_changes.py +clean_whitespace.py +detailed_check.py +check_fixed.py +final_lint_check.py diff --git a/Doc/reference/expressions.rst b/Doc/reference/expressions.rst index 7aa60cd7703bae..ad9f9186cdb83f 100644 --- a/Doc/reference/expressions.rst +++ b/Doc/reference/expressions.rst @@ -310,10 +310,10 @@ the iterable expression in the leftmost :keyword:`!for` clause, it is called an execution of the coroutine function in which it appears. See also :pep:`530`. -Note that this behavior extends to nested comprehensions: if a comprehension -contains an asynchronous comprehension in a nested scope, the outer comprehension -implicitly becomes asynchronous. This also applies to generator expressions that -contain asynchronous comprehensions. +Note that this behavior extends to nested scopes: if a comprehension or generator +expression contains asynchronous comprehensions (but not asynchronous generator +expressions) in nested scopes, the outer comprehension or generator expression +implicitly becomes asynchronous. .. versionadded:: 3.6 Asynchronous comprehensions were introduced. @@ -489,16 +489,21 @@ clauses or :keyword:`await` expressions it is called an expression returns a new asynchronous generator object, which is an asynchronous iterator (see :ref:`async-iterators`). -Note that a generator expression becomes an asynchronous generator expression -if it contains asynchronous comprehensions in nested scopes, such as an async -list comprehension within the generator expression. For example:: +Note that a generator expression also becomes an asynchronous generator expression +if it contains asynchronous comprehensions (list, set, or dict comprehensions with +:keyword:`!async for`) in nested scopes. For example:: # This becomes an async generator expression ([a async for a in async_iterable] for x in [1]) -This behavior occurs because the inner async comprehension requires an -asynchronous context to execute, which causes the entire generator expression -to become asynchronous. +However, a generator expression that contains an asynchronous generator expression +does *not* become asynchronous itself:: + + # This remains a regular generator expression + ((a async for a in async_iterable) for x in [1]) + +This distinction exists because async comprehensions require immediate evaluation +in an async context, while async generator expressions are lazily evaluated. .. versionadded:: 3.6 Asynchronous generator expressions were introduced. From 322e1819fac5dbae03327079df56a25b850d97ae Mon Sep 17 00:00:00 2001 From: tangyuan0821 Date: Sun, 24 Aug 2025 15:15:46 +0800 Subject: [PATCH 3/5] Clean up the .gitignore file --- .gitignore | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/.gitignore b/.gitignore index 286f86dc25ca6e..8c43d2c26ab76c 100644 --- a/.gitignore +++ b/.gitignore @@ -178,16 +178,4 @@ CLAUDE.local.md #### main branch only stuff below this line, things to backport go above. #### # main branch only: ABI files are not checked/maintained. Doc/data/python*.abi -validation_test.py -test_nested_difference.py -commit_message.txt -demo_issue_138097.py -PR_template.md -check_format.py -lint_check.py -Doc/reference/expressions.rst -check_our_changes.py -clean_whitespace.py -detailed_check.py -check_fixed.py -final_lint_check.py + From ba250645ce4f84fc91e232ef0cacdcfb922f6007 Mon Sep 17 00:00:00 2001 From: tangyuan0821 Date: Sun, 24 Aug 2025 15:17:00 +0800 Subject: [PATCH 4/5] Clean up the .gitignore file --- .gitignore | 1 - 1 file changed, 1 deletion(-) diff --git a/.gitignore b/.gitignore index 8c43d2c26ab76c..e842676d866bf8 100644 --- a/.gitignore +++ b/.gitignore @@ -178,4 +178,3 @@ CLAUDE.local.md #### main branch only stuff below this line, things to backport go above. #### # main branch only: ABI files are not checked/maintained. Doc/data/python*.abi - From 52b46c0ad2231b8d4f4e23fb4011155c781f389d Mon Sep 17 00:00:00 2001 From: tangyuan0821 Date: Sun, 24 Aug 2025 20:57:26 +0800 Subject: [PATCH 5/5] Corrective Documentation: Clarifying the Behavior of Asynchronous Generator Expressions in Immediate Nested Scopes --- Doc/reference/expressions.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Doc/reference/expressions.rst b/Doc/reference/expressions.rst index ad9f9186cdb83f..986600c2e04a5d 100644 --- a/Doc/reference/expressions.rst +++ b/Doc/reference/expressions.rst @@ -310,9 +310,9 @@ the iterable expression in the leftmost :keyword:`!for` clause, it is called an execution of the coroutine function in which it appears. See also :pep:`530`. -Note that this behavior extends to nested scopes: if a comprehension or generator +Note that this behavior extends to immediately nested scopes: if a comprehension or generator expression contains asynchronous comprehensions (but not asynchronous generator -expressions) in nested scopes, the outer comprehension or generator expression +expressions) in immediately nested scopes, the outer comprehension or generator expression implicitly becomes asynchronous. .. versionadded:: 3.6 @@ -491,7 +491,7 @@ which is an asynchronous iterator (see :ref:`async-iterators`). Note that a generator expression also becomes an asynchronous generator expression if it contains asynchronous comprehensions (list, set, or dict comprehensions with -:keyword:`!async for`) in nested scopes. For example:: +:keyword:`!async for`) in immediately nested scopes. For example:: # This becomes an async generator expression ([a async for a in async_iterable] for x in [1])