[3.14] GH-135171: Revert async generator expressions behavior (#135352)

This commit is contained in:
Mikhail Efimov 2025-06-16 17:45:42 +03:00 committed by GitHub
parent 0eec8ddac3
commit 2ef78a85e4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 48 additions and 16 deletions

View file

@ -1835,6 +1835,20 @@ class AsyncGenAsyncioTest(unittest.TestCase):
res = self.loop.run_until_complete(run()) res = self.loop.run_until_complete(run())
self.assertEqual(res, [i * 2 for i in range(1, 10)]) self.assertEqual(res, [i * 2 for i in range(1, 10)])
def test_async_gen_expression_incorrect(self):
async def ag():
yield 42
async def run(arg):
(x async for x in arg)
err_msg_async = "'async for' requires an object with " \
"__aiter__ method, got .*"
self.loop.run_until_complete(run(ag()))
with self.assertRaisesRegex(TypeError, err_msg_async):
self.loop.run_until_complete(run(None))
def test_asyncgen_nonstarted_hooks_are_cancellable(self): def test_asyncgen_nonstarted_hooks_are_cancellable(self):
# See https://bugs.python.org/issue38013 # See https://bugs.python.org/issue38013
messages = [] messages = []

View file

@ -2267,7 +2267,7 @@ class CoroutineTest(unittest.TestCase):
def test_call_aiter_once_in_comprehension(self): def test_call_aiter_once_in_comprehension(self):
class Iterator: class AsyncIterator:
def __init__(self): def __init__(self):
self.val = 0 self.val = 0
@ -2283,12 +2283,17 @@ class CoroutineTest(unittest.TestCase):
class C: class C:
def __aiter__(self): def __aiter__(self):
return Iterator() return AsyncIterator()
async def run(): async def run_listcomp():
return [i async for i in C()] return [i async for i in C()]
self.assertEqual(run_async(run()), ([], [1,2])) async def run_asyncgen():
ag = (i async for i in C())
return [i async for i in ag]
self.assertEqual(run_async(run_listcomp()), ([], [1, 2]))
self.assertEqual(run_async(run_asyncgen()), ([], [1, 2]))
@unittest.skipIf( @unittest.skipIf(

View file

@ -288,7 +288,7 @@ class GeneratorTest(unittest.TestCase):
def __iter__(self): def __iter__(self):
return Iterator() return Iterator()
self.assertEqual([1,2], list(i for i in C())) self.assertEqual([1, 2], list(i for i in C()))
class ModifyUnderlyingIterableTest(unittest.TestCase): class ModifyUnderlyingIterableTest(unittest.TestCase):

View file

@ -770,7 +770,7 @@ class ListComprehensionTest(unittest.TestCase):
def __iter__(self): def __iter__(self):
return Iterator() return Iterator()
self.assertEqual([1,2], [i for i in C()]) self.assertEqual([1, 2], [i for i in C()])
__test__ = {'doctests' : doctests} __test__ = {'doctests' : doctests}

View file

@ -0,0 +1,2 @@
Reverts the behavior of async generator expressions when created with object
w/o __aiter__ method to the pre-3.13 behavior of raising a TypeError.

View file

@ -4411,7 +4411,6 @@ codegen_sync_comprehension_generator(compiler *c, location loc,
comprehension_ty gen = (comprehension_ty)asdl_seq_GET(generators, comprehension_ty gen = (comprehension_ty)asdl_seq_GET(generators,
gen_index); gen_index);
int is_outer_genexpr = gen_index == 0 && type == COMP_GENEXP;
if (!iter_on_stack) { if (!iter_on_stack) {
if (gen_index == 0) { if (gen_index == 0) {
assert(METADATA(c)->u_argcount == 1); assert(METADATA(c)->u_argcount == 1);
@ -4442,15 +4441,13 @@ codegen_sync_comprehension_generator(compiler *c, location loc,
} }
if (IS_JUMP_TARGET_LABEL(start)) { if (IS_JUMP_TARGET_LABEL(start)) {
VISIT(c, expr, gen->iter); VISIT(c, expr, gen->iter);
ADDOP(c, LOC(gen->iter), GET_ITER);
} }
} }
} }
if (IS_JUMP_TARGET_LABEL(start)) { if (IS_JUMP_TARGET_LABEL(start)) {
depth++; depth++;
if (!is_outer_genexpr) {
ADDOP(c, LOC(gen->iter), GET_ITER);
}
USE_LABEL(c, start); USE_LABEL(c, start);
ADDOP_JUMP(c, LOC(gen->iter), FOR_ITER, anchor); ADDOP_JUMP(c, LOC(gen->iter), FOR_ITER, anchor);
} }
@ -4544,9 +4541,9 @@ codegen_async_comprehension_generator(compiler *c, location loc,
else { else {
/* Sub-iter - calculate on the fly */ /* Sub-iter - calculate on the fly */
VISIT(c, expr, gen->iter); VISIT(c, expr, gen->iter);
ADDOP(c, LOC(gen->iter), GET_AITER);
} }
} }
ADDOP(c, LOC(gen->iter), GET_AITER);
USE_LABEL(c, start); USE_LABEL(c, start);
/* Runtime will push a block here, so we need to account for that */ /* Runtime will push a block here, so we need to account for that */
@ -4758,6 +4755,19 @@ pop_inlined_comprehension_state(compiler *c, location loc,
return SUCCESS; return SUCCESS;
} }
static inline int
codegen_comprehension_iter(compiler *c, comprehension_ty comp)
{
VISIT(c, expr, comp->iter);
if (comp->is_async) {
ADDOP(c, LOC(comp->iter), GET_AITER);
}
else {
ADDOP(c, LOC(comp->iter), GET_ITER);
}
return SUCCESS;
}
static int static int
codegen_comprehension(compiler *c, expr_ty e, int type, codegen_comprehension(compiler *c, expr_ty e, int type,
identifier name, asdl_comprehension_seq *generators, expr_ty elt, identifier name, asdl_comprehension_seq *generators, expr_ty elt,
@ -4776,9 +4786,10 @@ codegen_comprehension(compiler *c, expr_ty e, int type,
location loc = LOC(e); location loc = LOC(e);
outermost = (comprehension_ty) asdl_seq_GET(generators, 0); outermost = (comprehension_ty) asdl_seq_GET(generators, 0);
int is_sync_genexpr = type == COMP_GENEXP && !outermost->is_async;
if (is_inlined) { if (is_inlined) {
VISIT(c, expr, outermost->iter); if (codegen_comprehension_iter(c, outermost)) {
goto error;
}
if (push_inlined_comprehension_state(c, loc, entry, &inline_state)) { if (push_inlined_comprehension_state(c, loc, entry, &inline_state)) {
goto error; goto error;
} }
@ -4852,10 +4863,10 @@ codegen_comprehension(compiler *c, expr_ty e, int type,
} }
Py_CLEAR(co); Py_CLEAR(co);
VISIT(c, expr, outermost->iter); if (codegen_comprehension_iter(c, outermost)) {
if (is_sync_genexpr) { goto error;
ADDOP(c, loc, GET_ITER);
} }
ADDOP_I(c, loc, CALL, 0); ADDOP_I(c, loc, CALL, 0);
if (is_async_comprehension && type != COMP_GENEXP) { if (is_async_comprehension && type != COMP_GENEXP) {