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.
This commit is contained in:
parent
17f7c683d2
commit
3f6ffe059f
|
@ -145,6 +145,10 @@ mp_vm_return_kind_t mp_obj_gen_resume(mp_obj_t self_in, mp_obj_t send_value, mp_
|
||||||
size_t n_state = mp_decode_uint_value(self->code_state.fun_bc->bytecode);
|
size_t n_state = mp_decode_uint_value(self->code_state.fun_bc->bytecode);
|
||||||
self->code_state.ip = 0;
|
self->code_state.ip = 0;
|
||||||
*ret_val = self->code_state.state[n_state - 1];
|
*ret_val = self->code_state.state[n_state - 1];
|
||||||
|
// PEP479: if StopIteration is raised inside a generator it is replaced with RuntimeError
|
||||||
|
if (mp_obj_is_subclass_fast(MP_OBJ_FROM_PTR(mp_obj_get_type(*ret_val)), MP_OBJ_FROM_PTR(&mp_type_StopIteration))) {
|
||||||
|
*ret_val = mp_obj_new_exception_msg(&mp_type_RuntimeError, "generator raised StopIteration");
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -168,15 +172,6 @@ STATIC mp_obj_t gen_resume_and_raise(mp_obj_t self_in, mp_obj_t send_value, mp_o
|
||||||
return ret;
|
return ret;
|
||||||
|
|
||||||
case MP_VM_RETURN_EXCEPTION:
|
case MP_VM_RETURN_EXCEPTION:
|
||||||
// TODO: Optimization of returning MP_OBJ_STOP_ITERATION is really part
|
|
||||||
// of mp_iternext() protocol, but this function is called by other methods
|
|
||||||
// too, which may not handled MP_OBJ_STOP_ITERATION.
|
|
||||||
if (mp_obj_is_subclass_fast(MP_OBJ_FROM_PTR(mp_obj_get_type(ret)), MP_OBJ_FROM_PTR(&mp_type_StopIteration))) {
|
|
||||||
mp_obj_t val = mp_obj_exception_get_value(ret);
|
|
||||||
if (val == mp_const_none) {
|
|
||||||
return MP_OBJ_STOP_ITERATION;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
nlr_raise(ret);
|
nlr_raise(ret);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -216,11 +211,10 @@ STATIC mp_obj_t gen_instance_close(mp_obj_t self_in) {
|
||||||
case MP_VM_RETURN_YIELD:
|
case MP_VM_RETURN_YIELD:
|
||||||
mp_raise_msg(&mp_type_RuntimeError, "generator ignored GeneratorExit");
|
mp_raise_msg(&mp_type_RuntimeError, "generator ignored GeneratorExit");
|
||||||
|
|
||||||
// Swallow StopIteration & GeneratorExit (== successful close), and re-raise any other
|
// Swallow GeneratorExit (== successful close), and re-raise any other
|
||||||
case MP_VM_RETURN_EXCEPTION:
|
case MP_VM_RETURN_EXCEPTION:
|
||||||
// ret should always be an instance of an exception class
|
// ret should always be an instance of an exception class
|
||||||
if (mp_obj_is_subclass_fast(MP_OBJ_FROM_PTR(mp_obj_get_type(ret)), MP_OBJ_FROM_PTR(&mp_type_GeneratorExit)) ||
|
if (mp_obj_is_subclass_fast(MP_OBJ_FROM_PTR(mp_obj_get_type(ret)), MP_OBJ_FROM_PTR(&mp_type_GeneratorExit))) {
|
||||||
mp_obj_is_subclass_fast(MP_OBJ_FROM_PTR(mp_obj_get_type(ret)), MP_OBJ_FROM_PTR(&mp_type_StopIteration))) {
|
|
||||||
return mp_const_none;
|
return mp_const_none;
|
||||||
}
|
}
|
||||||
nlr_raise(ret);
|
nlr_raise(ret);
|
||||||
|
|
|
@ -13,34 +13,6 @@ g = gen2()
|
||||||
print(list(g))
|
print(list(g))
|
||||||
|
|
||||||
|
|
||||||
# Like above, but terminate subgen using StopIteration
|
|
||||||
def gen3():
|
|
||||||
yield 1
|
|
||||||
yield 2
|
|
||||||
raise StopIteration
|
|
||||||
|
|
||||||
def gen4():
|
|
||||||
print("here1")
|
|
||||||
print((yield from gen3()))
|
|
||||||
print("here2")
|
|
||||||
|
|
||||||
g = gen4()
|
|
||||||
print(list(g))
|
|
||||||
|
|
||||||
# Like above, but terminate subgen using StopIteration with value
|
|
||||||
def gen5():
|
|
||||||
yield 1
|
|
||||||
yield 2
|
|
||||||
raise StopIteration(123)
|
|
||||||
|
|
||||||
def gen6():
|
|
||||||
print("here1")
|
|
||||||
print((yield from gen5()))
|
|
||||||
print("here2")
|
|
||||||
|
|
||||||
g = gen6()
|
|
||||||
print(list(g))
|
|
||||||
|
|
||||||
# StopIteration from within a Python function, within a native iterator (map), within a yield from
|
# StopIteration from within a Python function, within a native iterator (map), within a yield from
|
||||||
def gen7(x):
|
def gen7(x):
|
||||||
if x < 3:
|
if x < 3:
|
||||||
|
|
|
@ -1,14 +0,0 @@
|
||||||
here1
|
|
||||||
3
|
|
||||||
here2
|
|
||||||
[1, 2]
|
|
||||||
here1
|
|
||||||
None
|
|
||||||
here2
|
|
||||||
[1, 2]
|
|
||||||
here1
|
|
||||||
123
|
|
||||||
here2
|
|
||||||
[1, 2]
|
|
||||||
444
|
|
||||||
[0, 1, 2]
|
|
|
@ -55,14 +55,14 @@ except StopIteration:
|
||||||
|
|
||||||
|
|
||||||
# Yet another variation - leaf generator gets GeneratorExit,
|
# Yet another variation - leaf generator gets GeneratorExit,
|
||||||
# but raises StopIteration instead. This still should close chain properly.
|
# and reraises a new GeneratorExit. This still should close chain properly.
|
||||||
def gen5():
|
def gen5():
|
||||||
yield 1
|
yield 1
|
||||||
try:
|
try:
|
||||||
yield 2
|
yield 2
|
||||||
except GeneratorExit:
|
except GeneratorExit:
|
||||||
print("leaf caught GeneratorExit and raised StopIteration instead")
|
print("leaf caught GeneratorExit and reraised GeneratorExit")
|
||||||
raise StopIteration(123)
|
raise GeneratorExit(123)
|
||||||
yield 3
|
yield 3
|
||||||
yield 4
|
yield 4
|
||||||
|
|
||||||
|
|
|
@ -1,20 +0,0 @@
|
||||||
-1
|
|
||||||
1
|
|
||||||
StopIteration
|
|
||||||
-1
|
|
||||||
1
|
|
||||||
2
|
|
||||||
leaf caught GeneratorExit and swallowed it
|
|
||||||
delegating caught GeneratorExit
|
|
||||||
StopIteration
|
|
||||||
-1
|
|
||||||
1
|
|
||||||
2
|
|
||||||
leaf caught GeneratorExit and raised StopIteration instead
|
|
||||||
delegating caught GeneratorExit
|
|
||||||
StopIteration
|
|
||||||
123
|
|
||||||
RuntimeError
|
|
||||||
0
|
|
||||||
1
|
|
||||||
close
|
|
|
@ -25,6 +25,20 @@ def gen3():
|
||||||
g3 = gen3()
|
g3 = gen3()
|
||||||
print(next(g3))
|
print(next(g3))
|
||||||
try:
|
try:
|
||||||
g3.throw(StopIteration)
|
g3.throw(KeyError)
|
||||||
|
except KeyError:
|
||||||
|
print('got KeyError from downstream!')
|
||||||
|
|
||||||
|
# case where a thrown exception is caught and stops the generator
|
||||||
|
def gen4():
|
||||||
|
try:
|
||||||
|
yield 1
|
||||||
|
yield 2
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
g4 = gen4()
|
||||||
|
print(next(g4))
|
||||||
|
try:
|
||||||
|
g4.throw(ValueError)
|
||||||
except StopIteration:
|
except StopIteration:
|
||||||
print('got StopIteration from downstream!')
|
print('got StopIteration')
|
||||||
|
|
|
@ -1,6 +0,0 @@
|
||||||
1
|
|
||||||
got ValueError from upstream!
|
|
||||||
str1
|
|
||||||
got TypeError from downstream!
|
|
||||||
123
|
|
||||||
got StopIteration from downstream!
|
|
|
@ -31,13 +31,14 @@ except StopIteration:
|
||||||
print("StopIteration")
|
print("StopIteration")
|
||||||
|
|
||||||
|
|
||||||
# Throwing StopIteration in response to close() is ok
|
# Throwing GeneratorExit in response to close() is ok
|
||||||
def gen2():
|
def gen2():
|
||||||
try:
|
try:
|
||||||
yield 1
|
yield 1
|
||||||
yield 2
|
yield 2
|
||||||
except:
|
except:
|
||||||
raise StopIteration
|
print('raising GeneratorExit')
|
||||||
|
raise GeneratorExit
|
||||||
|
|
||||||
g = gen2()
|
g = gen2()
|
||||||
next(g)
|
next(g)
|
||||||
|
|
|
@ -1,10 +0,0 @@
|
||||||
None
|
|
||||||
StopIteration
|
|
||||||
1
|
|
||||||
None
|
|
||||||
StopIteration
|
|
||||||
[1, 2]
|
|
||||||
None
|
|
||||||
StopIteration
|
|
||||||
None
|
|
||||||
ValueError
|
|
|
@ -0,0 +1,29 @@
|
||||||
|
# tests for correct PEP479 behaviour (introduced in Python 3.5)
|
||||||
|
|
||||||
|
# basic case: StopIteration is converted into a RuntimeError
|
||||||
|
def gen():
|
||||||
|
yield 1
|
||||||
|
raise StopIteration
|
||||||
|
g = gen()
|
||||||
|
print(next(g))
|
||||||
|
try:
|
||||||
|
next(g)
|
||||||
|
except RuntimeError:
|
||||||
|
print('RuntimeError')
|
||||||
|
|
||||||
|
# trying to continue a failed generator now raises StopIteration
|
||||||
|
try:
|
||||||
|
next(g)
|
||||||
|
except StopIteration:
|
||||||
|
print('StopIteration')
|
||||||
|
|
||||||
|
# throwing a StopIteration which is uncaught will be converted into a RuntimeError
|
||||||
|
def gen():
|
||||||
|
yield 1
|
||||||
|
yield 2
|
||||||
|
g = gen()
|
||||||
|
print(next(g))
|
||||||
|
try:
|
||||||
|
g.throw(StopIteration)
|
||||||
|
except RuntimeError:
|
||||||
|
print('RuntimeError')
|
|
@ -0,0 +1,5 @@
|
||||||
|
1
|
||||||
|
RuntimeError
|
||||||
|
StopIteration
|
||||||
|
1
|
||||||
|
RuntimeError
|
|
@ -352,7 +352,7 @@ def run_tests(pyb, tests, args, base_path="."):
|
||||||
# Some tests are known to fail with native emitter
|
# Some tests are known to fail with native emitter
|
||||||
# Remove them from the below when they work
|
# Remove them from the below when they work
|
||||||
if args.emit == 'native':
|
if args.emit == 'native':
|
||||||
skip_tests.update({'basics/%s.py' % t for t in 'gen_yield_from gen_yield_from_close gen_yield_from_ducktype gen_yield_from_exc gen_yield_from_executing gen_yield_from_iter gen_yield_from_send gen_yield_from_stopped gen_yield_from_throw gen_yield_from_throw2 gen_yield_from_throw3 generator1 generator2 generator_args generator_close generator_closure generator_exc generator_name generator_pend_throw generator_return generator_send'.split()}) # require yield
|
skip_tests.update({'basics/%s.py' % t for t in 'gen_yield_from gen_yield_from_close gen_yield_from_ducktype gen_yield_from_exc gen_yield_from_executing gen_yield_from_iter gen_yield_from_send gen_yield_from_stopped gen_yield_from_throw gen_yield_from_throw2 gen_yield_from_throw3 generator1 generator2 generator_args generator_close generator_closure generator_exc generator_name generator_pend_throw generator_return generator_send generator_pep479'.split()}) # require yield
|
||||||
skip_tests.update({'basics/%s.py' % t for t in 'bytes_gen class_store_class globals_del string_join'.split()}) # require yield
|
skip_tests.update({'basics/%s.py' % t for t in 'bytes_gen class_store_class globals_del string_join'.split()}) # require yield
|
||||||
skip_tests.update({'basics/async_%s.py' % t for t in 'def await await2 for for2 with with2 with_break with_return'.split()}) # require yield
|
skip_tests.update({'basics/async_%s.py' % t for t in 'def await await2 for for2 with with2 with_break with_return'.split()}) # require yield
|
||||||
skip_tests.update({'basics/%s.py' % t for t in 'try_reraise try_reraise2'.split()}) # require raise_varargs
|
skip_tests.update({'basics/%s.py' % t for t in 'try_reraise try_reraise2'.split()}) # require raise_varargs
|
||||||
|
|
Loading…
Reference in New Issue