xs = []
while True:
try:
raise ValueError
finally:
xs.append(1)
break
assert xs == [1]
xs = []
def return_in_finally():
try:
raise ValueError
finally:
xs.append(1)
return 2
assert return_in_finally() == 2
assert xs == [1]
xs = []
for i in range(3):
try:
raise ValueError
finally:
xs.append(i)
continue
assert xs == [0, 1, 2]
def swallow_then_bare_raise():
while True:
try:
raise ValueError('swallowed')
finally:
break
raise
try:
swallow_then_bare_raise()
assert False, 'expected RuntimeError'
except RuntimeError as e:
assert str(e) == 'No active exception to reraise'
xs = []
def raise_in_finally_during_return():
try:
return 1
finally:
xs.append(1)
raise ValueError('boom')
try:
raise_in_finally_during_return()
assert False, 'expected ValueError'
except ValueError as e:
assert str(e) == 'boom'
assert xs == [1]
order = []
def note(tag, value):
order.append(tag)
return value
def return_evaluation_order():
try:
return note('value', 42)
finally:
order.append('finally')
assert return_evaluation_order() == 42
assert order == ['value', 'finally']
def break_discards_return():
for i in range(3):
try:
return 1
finally:
break
return 'after loop'
assert break_discards_return() == 'after loop'
def continue_discards_return():
runs = []
for i in range(2):
try:
return 1
finally:
runs.append(i)
continue
return runs
assert continue_discards_return() == [0, 1]
def finally_overrides_return():
try:
return 'try'
finally:
return 'finally'
assert finally_overrides_return() == 'finally'
xs = []
while True:
try:
try:
raise ValueError
finally:
xs.append('inner')
finally:
xs.append('outer')
break
assert xs == ['inner', 'outer']
xs = []
while True:
try:
raise ValueError
finally:
try:
xs.append('inner-try')
finally:
xs.append('inner-finally')
xs.append('outer-finally')
break
assert xs == ['inner-try', 'inner-finally', 'outer-finally']
xs = []
try:
while True:
try:
break
finally:
xs.append('finally')
raise ValueError('from finally')
except ValueError as e:
xs.append(str(e))
assert xs == ['finally', 'from finally']
out = []
try:
try:
raise ValueError('original')
finally:
try:
raise TypeError('inner')
except TypeError:
pass
out.append('finally done')
except ValueError as e:
out.append(str(e))
assert out == ['finally done', 'original']
def bare_raise_in_inner_finally():
seen = []
try:
raise ValueError('original')
except ValueError:
try:
return seen
finally:
try:
raise
except ValueError as e:
seen.append(str(e))
assert bare_raise_in_inner_finally() == ['original']
for i in range(3):
try:
raise ValueError('x')
except ValueError as caught:
break
try:
caught
assert False, 'expected NameError'
except NameError:
pass
handler_cleanup = []
try:
try:
try:
raise ValueError('original')
except ValueError as abandoned:
raise TypeError('replacement')
finally:
try:
abandoned
except NameError:
handler_cleanup.append('unbound')
else:
handler_cleanup.append('bound')
except TypeError as replacement:
handler_cleanup.append(str(replacement))
assert handler_cleanup == ['unbound', 'replacement']
def exceptional_handler_cell_cleanup():
probe = None
try:
try:
raise ValueError('original')
except ValueError as captured:
probe = lambda: captured
raise TypeError('replacement')
except TypeError:
pass
try:
probe()
return 'still bound'
except NameError as e:
return str(e)
assert exceptional_handler_cell_cleanup() == (
"cannot access free variable 'captured' where it is not associated with a value in enclosing scope"
)
def return_from_handler():
try:
raise ValueError('x')
except ValueError as exc2:
return 'returned', lambda: exc2
value, probe = return_from_handler()
assert value == 'returned'
try:
probe()
assert False, 'expected NameError'
except NameError:
pass
def owner_read_after_cleanup():
try:
raise ValueError('x')
except ValueError as exc3:
hold = lambda: exc3
return exc3
try:
owner_read_after_cleanup()
assert False, 'expected UnboundLocalError'
except UnboundLocalError as e:
assert str(e) == "cannot access local variable 'exc3' where it is not associated with a value"
for i in range(2):
try:
raise ValueError('x')
except ValueError as caught2:
continue
try:
caught2
assert False, 'expected NameError'
except NameError:
pass
events = []
class Tracker:
def __enter__(self):
events.append('enter')
return self
def __exit__(self, typ, val, tb):
events.append('exit')
return False
while True:
try:
with Tracker():
raise ValueError
finally:
events.append('finally')
break
assert events == ['enter', 'exit', 'finally']
events = []
class ExplodingExit:
def __init__(self, tag):
self.tag = tag
def __enter__(self):
events.append(f'enter {self.tag}')
return self
def __exit__(self, typ, val, tb):
events.append(f'exit {self.tag}')
raise RuntimeError(f'exit failed {self.tag}')
def return_through_exploding_exit():
try:
with ExplodingExit('return'):
return 'discarded'
except RuntimeError as e:
return str(e)
assert return_through_exploding_exit() == 'exit failed return'
assert events == ['enter return', 'exit return']
events = []
try:
while True:
with ExplodingExit('break'):
break
except RuntimeError as e:
events.append(str(e))
assert events == ['enter break', 'exit break', 'exit failed break']
events = []
while True:
try:
raise ValueError
finally:
with Tracker():
events.append('body')
break
assert events == ['enter', 'body', 'exit']
events = []
def deep_unwind():
try:
for i in range(5):
with Tracker():
return 'done'
finally:
events.append('finally')
assert deep_unwind() == 'done'
assert events == ['enter', 'exit', 'finally']
def replace_exception():
try:
raise ValueError('original')
finally:
raise TypeError('replacement')
try:
replace_exception()
assert False, 'expected TypeError'
except TypeError as e:
assert str(e) == 'replacement'
xs = []
for mode in ['ok', 'caught', 'uncaught', 'ret']:
def run(mode=mode):
try:
if mode in ('caught', 'uncaught'):
raise KeyError(mode)
except KeyError as e:
xs.append(f'except {mode}')
if mode == 'uncaught':
raise ValueError(mode)
else:
xs.append(f'else {mode}')
if mode == 'ret':
return 'ret'
finally:
xs.append(f'finally {mode}')
try:
run()
except ValueError:
xs.append(f'caught {mode}')
assert xs == [
'else ok',
'finally ok',
'except caught',
'finally caught',
'except uncaught',
'finally uncaught',
'caught uncaught',
'else ret',
'finally ret',
]
xs = []
while True:
try:
raise ValueError
except ValueError:
xs.append('except')
break
finally:
xs.append('finally')
assert xs == ['except', 'finally']
def continue_from_handler_through_finally():
seen = []
for i in range(2):
try:
try:
raise ValueError(str(i))
except ValueError as current:
seen.append(f'handler {current}')
continue
finally:
try:
current
seen.append('bound')
except UnboundLocalError:
seen.append('unbound')
seen.append(f'finally {i}')
return seen
assert continue_from_handler_through_finally() == [
'handler 0',
'unbound',
'finally 0',
'handler 1',
'unbound',
'finally 1',
]
xs = []
for i in range(2):
try:
raise ValueError(str(i))
finally:
xs.append(i)
continue
else:
xs.append('else')
assert xs == [0, 1, 'else']
xs = []
for i in range(2):
try:
raise ValueError
finally:
break
else:
xs.append('else')
assert xs == []
xs = []
try:
try:
raise ValueError('kept')
finally:
for k in range(5):
xs.append(k)
break
xs.append('after inner loop')
except ValueError as e:
xs.append(str(e))
assert xs == [0, 'after inner loop', 'kept']
def break_in_handler_loop():
try:
raise ValueError('original')
except ValueError:
while True:
break
try:
raise
except ValueError as e:
return str(e)
assert break_in_handler_loop() == 'original'
def for_break_then_reraise():
try:
raise ValueError('kept by handler')
except ValueError:
for i in [1, 2]:
break
raise
try:
for_break_then_reraise()
assert False, 'expected ValueError'
except ValueError as e:
assert str(e) == 'kept by handler'
order = []
def return_two_finallys():
try:
try:
return 'value'
finally:
order.append('inner')
finally:
order.append('outer')
assert return_two_finallys() == 'value'
assert order == ['inner', 'outer']
order = []
def swallow_then_outer():
try:
try:
raise ValueError
finally:
order.append('inner')
return 'swallowed'
finally:
order.append('outer')
assert swallow_then_outer() == 'swallowed'
assert order == ['inner', 'outer']
xs = []
while True:
try:
raise KeyError('k')
except ValueError:
xs.append('wrong handler')
except KeyError:
xs.append('key')
break
finally:
xs.append('finally')
assert xs == ['key', 'finally']
events = []
class Recorder:
def __enter__(self):
events.append('enter')
return self
def __exit__(self, typ, val, tb):
events.append(f'exit {typ is None}')
return False
while True:
try:
raise ValueError
except ValueError:
with Recorder():
break
assert events == ['enter', 'exit True']
class Swallow:
def __enter__(self):
return self
def __exit__(self, typ, val, tb):
return True
xs = []
for i in range(3):
with Swallow():
raise ValueError(str(i))
xs.append(f'resumed {i}')
assert xs == ['resumed 0', 'resumed 1', 'resumed 2']
xs = []
for i in range(3):
with Swallow():
if i == 1:
raise ValueError('swallowed')
xs.append(i)
xs.append(f'after {i}')
assert xs == [0, 'after 0', 'after 1', 2, 'after 2']
events = []
while True:
with Recorder():
try:
raise ValueError
finally:
events.append('finally')
break
assert events == ['enter', 'finally', 'exit True']
events = []
try:
with Recorder():
raise ValueError('through exit')
except ValueError as e:
events.append(str(e))
assert events == ['enter', 'exit False', 'through exit']
events = []
def deep_mixed():
for i in range(3):
with Recorder():
try:
return 'deep'
finally:
events.append('finally')
assert deep_mixed() == 'deep'
assert events == ['enter', 'finally', 'exit True']
events = []
for i in range(2):
with Recorder():
try:
events.append(i)
continue
finally:
events.append('finally')
assert events == ['enter', 0, 'finally', 'exit True', 'enter', 1, 'finally', 'exit True']
def boom():
raise ValueError('boom')
def return_call_except():
try:
return boom()
except ValueError as e:
return f'caught {e}'
assert return_call_except() == 'caught boom'
def return_call_short_circuit():
try:
return False or boom()
except ValueError as e:
return f'caught {e}'
assert return_call_short_circuit() == 'caught boom'
class ReturnBoom:
def fail(self):
raise ValueError('attribute boom')
def return_attr_call_except():
try:
return ReturnBoom().fail()
except ValueError as e:
return f'caught {e}'
assert return_attr_call_except() == 'caught attribute boom'
def get_boom():
return boom
def return_indirect_call_except():
try:
return get_boom()()
except ValueError as e:
return f'caught {e}'
assert return_indirect_call_except() == 'caught boom'
def return_call_and_fallthrough():
try:
return True and boom()
except ValueError as e:
return f'caught {e}'
assert return_call_and_fallthrough() == 'caught boom'
def return_call_ternary_fallthrough():
try:
return 0 if False else boom()
except ValueError as e:
return f'caught {e}'
assert return_call_ternary_fallthrough() == 'caught boom'
def return_call_ternary_jump():
try:
return boom() if True else 0
except ValueError as e:
return f'caught {e}'
assert return_call_ternary_jump() == 'caught boom'
events = []
def return_call_finally():
try:
return boom()
finally:
events.append('finally')
try:
return_call_finally()
except ValueError as e:
events.append(str(e))
assert events == ['finally', 'boom']
events = []
def return_call_with():
with Recorder():
return boom()
try:
return_call_with()
except ValueError:
events.append('caught')
assert events == ['enter', 'exit False', 'caught']
events = []
def return_call_nested():
try:
try:
return boom()
except ValueError:
events.append('inner')
raise
finally:
events.append('finally')
try:
return_call_nested()
except ValueError:
events.append('outer')
assert events == ['inner', 'finally', 'outer']
def return_call_in_loop():
for i in range(3):
try:
return boom()
except ValueError:
return f'caught in loop {i}'
assert return_call_in_loop() == 'caught in loop 0'