Last active
September 1, 2017 23:42
-
-
Save wolever/60ccc1ab9f2e3175caacc0d12d5c80b8 to your computer and use it in GitHub Desktop.
Confusing bug I was hit with today: a `return` in a `finally` that ate an exception thrown from a generator
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
""" | |
A demonstration of a subtle bug when a context manager uses | |
`return` in a `finally` block around a `yield`. | |
See comments on last line for an explanation. | |
Take-away: be very, very careful about using `return` in a | |
`finally` block. | |
""" | |
from contextlib import contextmanager | |
@contextmanager | |
def some_context_manager(): | |
needs_cleanup = False | |
try: | |
yield 42 | |
finally: | |
if not needs_cleanup: | |
return | |
cleanup() | |
def function_with_error(): | |
raise Exception("oh no!") | |
with some_context_manager(): | |
foo = function_with_error() | |
print "Foo:", foo # <-- will fail because the variable `foo` isn't defined | |
""" | |
Error: | |
Traceback (most recent call last): | |
File "return-in-finally.py", line 26, in <module> | |
print "Foo:", foo | |
NameError: name 'foo' is not defined | |
Explanation: because `function_with_error()` raises an exception, the | |
`foo = function_with_error()` statement is never executed, so `foo` is | |
never assigned to. | |
The `return` in the `finally` block of `some_context_manager()` eats | |
the exception and causes a `StopIteration()` to be raised instead | |
(because the `some_context_manager()` is a generator not a regular | |
function). The `StopIteration()` is eaten by the the `@contextmanager` | |
decorator, and executaion continues like normal after the `with` block. | |
""" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment