8000 py/objgenerator: Implement PEP479, StopIteration convs to RuntimeError. · jimmo/micropython@3f6ffe0 · GitHub 6601
[go: up one dir, main page]

Skip to content

Commit 3f6ffe0

Browse files
committed
py/objgenerator: Implement PEP479, StopIteration convs to RuntimeError.
This commit implements PEP479 which disallows raising StopIteration inside a generator to signal that it should be finished. Instead, the generator should simply return when it is complete. See https://www.python.org/dev/peps/pep-0479/ for details.
1 parent 17f7c68 commit 3f6ffe0

12 files changed

+63
-98
lines changed

py/objgenerator.c

Lines changed: 6 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,10 @@ mp_vm_return_kind_t mp_obj_gen_resume(mp_obj_t self_in, mp_obj_t send_value, mp_
145145
size_t n_state = mp_decode_uint_value(self->code_state.fun_bc->bytecode);
146146
self->code_state.ip = 0;
147147
*ret_val = self->code_state.state[n_state - 1];
148+
// PEP479: if StopIteration is raised inside a generator it is replaced with RuntimeError
149+
if (mp_obj_is_subclass_fast(MP_OBJ_FROM_PTR(mp_obj_get_type(*ret_val)), MP_OBJ_FROM_PTR(&mp_type_StopIteration))) {
150+
*ret_val = mp_obj_new_exception_msg(&mp_type_RuntimeError, "generator raised StopIteration");
151+
}
148152
break;
149153
}
150154
}
@@ -168,15 +172,6 @@ STATIC mp_obj_t gen_resume_and_raise(mp_obj_t self_in, mp_obj_t send_value, mp_o
168172
return ret;
169173

170174
case MP_VM_RETURN_EXCEPTION:
171-
// TODO: Optimization of returning MP_OBJ_STOP_ITERATION is really part
172-
// of mp_iternext() protocol, but this function is called by other methods
173-
// too, which may not handled MP_OBJ_STOP_ITERATION.
174-
if (mp_obj_is_subclass_fast(MP_OBJ_FROM_PTR(mp_obj_get_type(ret)), MP_OBJ_FROM_PTR(&mp_type_StopIteration))) {
175-
mp_obj_t val = mp_obj_exception_get_value(ret);
176-
if (val == mp_const_none) {
177-
return MP_OBJ_STOP_ITERATION;
178-
}
179-
}
180175
nlr_raise(ret);
181176
}
182177
}
@@ -216,11 +211,10 @@ STATIC mp_obj_t gen_instance_close(mp_obj_t self_in) {
216211
case MP_VM_RETURN_YIELD:
217212
mp_raise_msg(&mp_type_RuntimeError, "generator ignored GeneratorExit");
218213

219-
// Swallow StopIteration & GeneratorExit (== successful close), and re-raise any other
214+
// Swallow GeneratorExit (== successful close), and re-raise any other
220215
case MP_VM_RETURN_EXCEPTION:
221216
// ret should always be an instance of an exception class
222-
if (mp_obj_is_subclass_fast(MP_OBJ_FROM_PTR(mp_obj_get_type(ret)), MP_OBJ_FROM_PTR(&mp_type_GeneratorExit)) ||
223-
mp_obj_is_subclass_fast(MP_OBJ_FROM_PTR(mp_obj_get_type(ret)), MP_OBJ_FROM_PTR(&mp_type_StopIteration))) {
217+
if (mp_obj_is_subclass_fast(MP_OBJ_FROM_PTR(mp_obj_get_type(ret)), MP_OBJ_FROM_PTR(&mp_type_GeneratorExit))) {
224218
return mp_const_none;
225219
}
226220
nlr_raise(ret);

tests/basics/gen_yield_from.py

Lines changed: 0 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -13,34 +13,6 @@ def gen2():
1313
print(list(g))
1414

1515

16-
# Like above, but terminate subgen using StopIteration
17-
def gen3():
18-
yield 1
19-
yield 2
20-
raise StopIteration
21-
22-
def gen4():
23-
print("here1")
24-
print((yield from gen3()))
25-
print("here2")
26-
27-
g = gen4()
28-
print(list(g))
29-
30-
# Like above, but terminate subgen using StopIteration with value
31-
def gen5():
32-
yield 1
33-
yield 2
34-
raise StopIteration(123)
35-
36-
def gen6():
37-
print("here1")
38-
print((yield from gen5()))
39-
print("here2")
40-
41-
g = gen6()
42-
print(list(g))
43-
4416
# StopIteration from within a Python function, within a native iterator (map), within a yield from
4517
def gen7(x):
4618
if x < 3:

tests/basics/gen_yield_from.py.exp

Lines changed: 0 additions & 14 deletions
This file was deleted.

tests/basics/gen_yield_from_close.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -55,14 +55,14 @@ def gen4():
5555

5656

5757
# Yet another variation - leaf generator gets GeneratorExit,
58-
# but raises StopIteration instead. This still should close chain properly.
58+
# and reraises a new GeneratorExit. This still should close chain properly.
5959
def gen5():
6060
yield 1
6161
try:
6262
yield 2
6363
except GeneratorExit:
64-
print("leaf caught GeneratorExit and raised StopIteration instead")
65-
raise StopIteration(123)
64+
print("leaf caught GeneratorExit and reraised GeneratorExit")
65+
raise GeneratorExit(123)
6666
yield 3
6767
yield 4
6868

tests/basics/gen_yield_from_close.py.exp

Lines changed: 0 additions & 20 deletions
This file was deleted.

tests/basics/gen_yield_from_throw.py

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,20 @@ def gen3():
2525
g3 = gen3()
2626
print(next(g3))
2727
try:
28-
g3.throw(StopIteration)
28+
g3.throw(KeyError)
29+
except KeyError:
30+
print('got KeyError from downstream!')
31+
32+
# case where a thrown exception is caught and stops the generator
33+
def gen4():
34+
try:
35+
yield 1
36+
yield 2
37+
except:
38+
pass
39+
g4 = gen4()
40+
print(next(g4))
41+
try:
42+
g4.throw(ValueError)
2943
except StopIteration:
30-
print('got StopIteration from downstream!')
44+
print('got StopIteration')

tests/basics/gen_yield_from_throw.py.exp

Lines changed: 0 additions & 6 deletions
This file was deleted.

tests/basics/generator_close.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,13 +31,14 @@ def gen1():
3131
print("StopIteration")
3232

3333

34-
# Throwing StopIteration in response to close() is ok
34+
# Throwing GeneratorExit in response to close() is ok
3535
def gen2():
3636
try:
3737
yield 1
3838
yield 2
3939
except:
40-
raise StopIteration
40+
print('raising GeneratorExit')
41+
raise GeneratorExit
4142

4243
g = gen2()
4344
next(g)

tests/basics/generator_close.py.exp

Lines changed: 0 additions & 10 deletions
This file was deleted.

tests/basics/generator_pep479.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# tests for correct PEP479 behaviour (introduced in Python 3.5)
2+
3+
# basic case: StopIteration is converted into a RuntimeError
4+
def gen():
5+
yield 1
6+
raise StopIteration
7+
g = gen()
8+
print(next(g))
9+
try:
10+
next(g)
11+
except RuntimeError:
12+
print('RuntimeError')
13+
14+
# trying to continue a failed generator now raises StopIteration
15+
try:
16+
next(g)
17+
except StopIteration:
18+
print('StopIteration')
19+
20+
# throwing a StopIteration which is uncaught will be converted into a RuntimeError
21+
def gen():
22+
yield 1
23+
yield 2
24+
g = gen()
25+
print(next(g))
26+
try:
27+
g.throw(StopIteration)
28+
except RuntimeError:
29+
print('RuntimeError')

0 commit comments

Comments
 (0)
0