Skip to content

Instantly share code, notes, and snippets.

@wolever
Last active September 1, 2017 23:42
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save wolever/60ccc1ab9f2e3175caacc0d12d5c80b8 to your computer and use it in GitHub Desktop.
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
"""
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