Skip to content

Commit 1749531

Browse files
committed
Backport 135115
1 parent 8c28241 commit 1749531

File tree

7 files changed

+89
-17
lines changed

7 files changed

+89
-17
lines changed

Lib/test/test_generators.py

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -296,21 +296,26 @@ def gen(it):
296296
yield x
297297
return gen(range(10))
298298

299-
def process_tests(self, get_generator):
299+
def process_tests(self, get_generator, is_expr):
300+
err_iterator = "'.*' object is not an iterator"
301+
err_iterable = "'.*' object is not iterable"
300302
for obj in self.iterables:
301303
g_obj = get_generator(obj)
302304
with self.subTest(g_obj=g_obj, obj=obj):
303-
self.assertListEqual(list(g_obj), list(obj))
305+
if is_expr:
306+
self.assertRaisesRegex(TypeError, err_iterator, list, g_obj)
307+
else:
308+
self.assertListEqual(list(g_obj), list(obj))
304309

305310
g_iter = get_generator(iter(obj))
306311
with self.subTest(g_iter=g_iter, obj=obj):
307312
self.assertListEqual(list(g_iter), list(obj))
308313

309-
err_regex = "'.*' object is not iterable"
310314
for obj in self.non_iterables:
311315
g_obj = get_generator(obj)
312316
with self.subTest(g_obj=g_obj):
313-
self.assertRaisesRegex(TypeError, err_regex, list, g_obj)
317+
err = err_iterator if is_expr else err_iterable
318+
self.assertRaisesRegex(TypeError, err, list, g_obj)
314319

315320
def test_modify_f_locals(self):
316321
def modify_f_locals(g, local, obj):
@@ -323,8 +328,8 @@ def get_generator_genexpr(obj):
323328
def get_generator_genfunc(obj):
324329
return modify_f_locals(self.genfunc(), 'it', obj)
325330

326-
self.process_tests(get_generator_genexpr)
327-
self.process_tests(get_generator_genfunc)
331+
self.process_tests(get_generator_genexpr, True)
332+
self.process_tests(get_generator_genfunc, False)
328333

329334
def test_new_gen_from_gi_code(self):
330335
def new_gen_from_gi_code(g, obj):
@@ -337,8 +342,8 @@ def get_generator_genexpr(obj):
337342
def get_generator_genfunc(obj):
338343
return new_gen_from_gi_code(self.genfunc(), obj)
339344

340-
self.process_tests(get_generator_genexpr)
341-
self.process_tests(get_generator_genfunc)
345+
self.process_tests(get_generator_genexpr, True)
346+
self.process_tests(get_generator_genfunc, False)
342347

343348

344349
class ExceptionTest(unittest.TestCase):

Lib/test/test_genexps.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,14 @@
131131
>>> list(g)
132132
[1, 9, 25, 49, 81]
133133
134+
Verify that the outermost for-expression makes an immediate check
135+
for iterability
136+
>>> (i for i in 6)
137+
Traceback (most recent call last):
138+
File "<pyshell#4>", line 1, in -toplevel-
139+
(i for i in 6)
140+
TypeError: 'int' object is not iterable
141+
134142
Verify late binding for the innermost for-expression
135143
136144
>>> g = ((i,j) for i in range(3) for j in range(x))
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
Reverted change made in 3.13.4 where creating a generator from an object
2+
that was not an iterable did not immediately raise an exception. Code like
3+
this::
4+
5+
def is_iterable(x):
6+
try:
7+
(_ for _ in x)
8+
return True
9+
except TypeError
10+
return False
11+
12+
now works again.

Python/bytecodes.c

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2599,7 +2599,14 @@ dummy_func(
25992599

26002600
replaced op(_FOR_ITER, (iter -- iter, next)) {
26012601
/* before: [iter]; after: [iter, iter()] *or* [] (and jump over END_FOR.) */
2602-
next = (*Py_TYPE(iter)->tp_iternext)(iter);
2602+
iternextfunc func = Py_TYPE(iter)->tp_iternext;
2603+
if (func == NULL) {
2604+
_PyErr_Format(tstate, PyExc_TypeError,
2605+
"'%.100s' object is not an iterator",
2606+
Py_TYPE(iter_o)->tp_name);
2607+
ERROR_NO_POP();
2608+
}
2609+
next = func(iter);
26032610
if (next == NULL) {
26042611
if (_PyErr_Occurred(tstate)) {
26052612
if (!_PyErr_ExceptionMatches(tstate, PyExc_StopIteration)) {
@@ -2622,7 +2629,14 @@ dummy_func(
26222629

26232630
op(_FOR_ITER_TIER_TWO, (iter -- iter, next)) {
26242631
/* before: [iter]; after: [iter, iter()] *or* [] (and jump over END_FOR.) */
2625-
next = (*Py_TYPE(iter)->tp_iternext)(iter);
2632+
iternextfunc func = Py_TYPE(iter)->tp_iternext;
2633+
if (func == NULL) {
2634+
_PyErr_Format(tstate, PyExc_TypeError,
2635+
"'%.100s' object is not an iterator",
2636+
Py_TYPE(iter_o)->tp_name);
2637+
ERROR_NO_POP();
2638+
}
2639+
next = func(iter);
26262640
if (next == NULL) {
26272641
if (_PyErr_Occurred(tstate)) {
26282642
if (!_PyErr_ExceptionMatches(tstate, PyExc_StopIteration)) {
@@ -2643,7 +2657,14 @@ dummy_func(
26432657
inst(INSTRUMENTED_FOR_ITER, (unused/1 -- )) {
26442658
_Py_CODEUNIT *target;
26452659
PyObject *iter = TOP();
2646-
PyObject *next = (*Py_TYPE(iter)->tp_iternext)(iter);
2660+
iternextfunc func = Py_TYPE(iter)->tp_iternext;
2661+
if (func == NULL) {
2662+
_PyErr_Format(tstate, PyExc_TypeError,
2663+
"'%.100s' object is not an iterator",
2664+
Py_TYPE(iter_o)->tp_name);
2665+
ERROR_NO_POP();
2666+
}
2667+
next = func(iter);
26472668
if (next != NULL) {
26482669
PUSH(next);
26492670
target = next_instr;

Python/compile.c

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5368,7 +5368,7 @@ compiler_sync_comprehension_generator(struct compiler *c, location loc,
53685368

53695369
comprehension_ty gen = (comprehension_ty)asdl_seq_GET(generators,
53705370
gen_index);
5371-
5371+
int is_outer_genexpr = gen_index == 0 && type == COMP_GENEXP;
53725372
if (!iter_on_stack) {
53735373
if (gen_index == 0) {
53745374
/* Receive outermost iter as an implicit argument */
@@ -5407,7 +5407,9 @@ compiler_sync_comprehension_generator(struct compiler *c, location loc,
54075407

54085408
if (IS_LABEL(start)) {
54095409
depth++;
5410-
ADDOP(c, LOC(gen->iter), GET_ITER);
5410+
if (!is_outer_genexpr) {
5411+
ADDOP(c, LOC(gen->iter), GET_ITER);
5412+
}
54115413
USE_LABEL(c, start);
54125414
ADDOP_JUMP(c, LOC(gen->iter), FOR_ITER, anchor);
54135415
}
@@ -5810,6 +5812,7 @@ compiler_comprehension(struct compiler *c, expr_ty e, int type,
58105812
location loc = LOC(e);
58115813

58125814
outermost = (comprehension_ty) asdl_seq_GET(generators, 0);
5815+
int is_sync_genexpr = type == COMP_GENEXP && !outermost->is_async;
58135816
if (is_inlined) {
58145817
if (compiler_visit_expr(c, outermost->iter) < 0) {
58155818
goto error;
@@ -5898,7 +5901,9 @@ compiler_comprehension(struct compiler *c, expr_ty e, int type,
58985901
Py_CLEAR(co);
58995902

59005903
VISIT(c, expr, outermost->iter);
5901-
5904+
if (is_sync_genexpr) {
5905+
ADDOP(c, loc, GET_ITER);
5906+
}
59025907
ADDOP_I(c, loc, CALL, 0);
59035908

59045909
if (is_async_generator && type != COMP_GENEXP) {

Python/executor_cases.c.h

Lines changed: 8 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Python/generated_cases.c.h

Lines changed: 16 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)