only caught ValueError (xyz) >>> import warnings >>> old_filters = warnings.filters.copy() >>> warnings.filterwarnings("ignore", category=DeprecationWarning) # Filter DeprecationWarning: regarding the (type, val, tb) signature of throw(). # Deprecation warnings are re-enabled below. >>> g.throw(ValueError, ValueError(1)) # value+matching type caught ValueError (1) >>> g.throw(ValueError, TypeError(1)) # mismatched type, rewrapped caught ValueError (1) >>> g.throw(ValueError, ValueError(1), None) # explicit None traceback caught ValueError (1) >>> g.throw(ValueError(1), "foo") # bad args Traceback (most recent call last): ... TypeError: instance exception may not have a separate value >>> g.throw(ValueError, "foo", 23) # bad args Traceback (most recent call last): ... TypeError: throw() third argument must be a traceback object >>> g.throw("abc") Traceback (most recent call last): ... TypeError: exceptions must be classes or instances deriving from BaseException, not str >>> g.throw(0) Traceback (most recent call last): ... TypeError: exceptions must be classes or instances deriving from BaseException, not int >>> g.throw(list) Traceback (most recent call last): ... TypeError: exceptions must be classes or instances deriving from BaseException, not type >>> def throw(g,exc): ... try: ... raise exc ... except: ... g.throw(*sys.exc_info()) >>> throw(g,ValueError) # do it with traceback included caught ValueError () >>> g.send(1) 1 >>> throw(g,TypeError) # terminate the generator Traceback (most recent call last): ... TypeError >>> print(g.gi_frame) None >>> g.send(2) Traceback (most recent call last): ... StopIteration >>> g.throw(ValueError,6) # throw on closed generator Traceback (most recent call last): ... ValueError: 6 >>> f().throw(ValueError,7) # throw on just-opened generator Traceback (most recent call last): ... ValueError: 7 >>> warnings.filters[:] = old_filters # Re-enable DeprecationWarning: the (type, val, tb) exception representation is deprecated, # and may be removed in a future version of Python. Plain "raise" inside a generator should preserve the traceback (#13188). The traceback should have 3 levels: - g.throw() - f() - 1/0 >>> def f(): ... try: ... yield ... except: ... raise >>> g = f() >>> try: ... 1/0 ... except ZeroDivisionError as v: ... try: ... g.throw(v) ... except Exception as w: ... tb = w.__traceback__ >>> levels = 0 >>> while tb: ... levels += 1 ... tb = tb.tb_next >>> levels 3 Now let's try closing a generator: >>> def f(): ... try: yield ... except GeneratorExit: ... print("exiting") >>> g = f() >>> next(g) >>> g.close() exiting >>> g.close() # should be no-op now >>> f().close() # close on just-opened generator should be fine >>> def f(): yield # an even simpler generator >>> f().close() # close before opening >>> g = f() >>> next(g) >>> g.close() # close normally And finalization: >>> def f(): ... try: yield ... finally: ... print("exiting") >>> g = f() >>> next(g) >>> del g; gc_collect() # For PyPy or other GCs. exiting GeneratorExit is not caught by except Exception: >>> def f(): ... try: yield ... except Exception: ... print('except') ... finally: ... print('finally') >>> g = f() >>> next(g) >>> del g; gc_collect() # For PyPy or other GCs. finally Now let's try some ill-behaved generators: >>> def f(): ... try: yield ... except GeneratorExit: ... yield "foo!" >>> g = f() >>> next(g) >>> g.close() Traceback (most recent call last): ... RuntimeError: generator ignored GeneratorExit >>> g.close() Our ill-behaved code should be invoked during GC: >>> with support.catch_unraisable_exception() as cm: ... g = f() ... next(g) ... del g ... ... cm.unraisable.exc_type == RuntimeError ... "generator ignored GeneratorExit" in str(cm.unraisable.exc_value) ... cm.unraisable.exc_traceback is not None True True True And errors thrown during closing should propagate: >>> def f(): ... try: yield ... except GeneratorExit: ... raise TypeError("fie!") >>> g = f() >>> next(g) >>> g.close() Traceback (most recent call last): ... TypeError: fie! Ensure that various yield expression constructs make their enclosing function a generator: >>> def f(): x += yield >>> type(f()) >>> def f(): x = yield >>> type(f()) >>> def f(): lambda x=(yield): 1 >>> type(f()) >>> def f(d): d[(yield "a")] = d[(yield "b")] = 27 >>> data = [1,2] >>> g = f(data) >>> type(g) >>> g.send(None) 'a' >>> data [1, 2] >>> g.send(0) 'b' >>> data [27, 2] >>> try: g.send(1) ... except StopIteration: pass >>> data [27, 27] ak