[Python-Dev] Allow __enter__() methods to skip the with statement body?
ncoghlan at gmail.com
Wed Feb 25 13:24:33 CET 2009
An interesting discrepancy  has been noted when comparing
contextlib.nested (and contextlib.contextmanager) with the equivalent
nested with statements.
Specifically, the following examples behave differently if
cmB().__enter__() raises an exception which cmA().__exit__() then
handles (and suppresses):
# This will resume here without executing "Do stuff"
# This will raise RuntimeError complaining that the underlying
# generator didn't yield
with contextlib.nested(cmA(), cmB()):
# This will raise the same RuntimeError as the contextmanager
# example (unsurprising, given the way nested() is implemented)
The problem arises any time it is possible to skip over the yield
statement in a contextlib.contextmanager based context manager without
raising an exception that can be seen by the code calling __enter__().
I think the right way to fix this (as suggested by the original poster
of the bug report) is to introduce a new flow control exception along
the lines of GeneratorExit (e.g. SkipContext) and tweak the expansion of
the with statement  to skip the body of the statement if __enter__()
throws that specific exception:
mgr = (EXPR)
exit = mgr.__exit__ # Not calling it yet
value = mgr.__enter__()
pass # This exception handler is the new part...
exc = True
VAR = value # Only if "as VAR" is present
# The exceptional case is handled here
exc = False
if not exit(*sys.exc_info()):
# The exception is swallowed if exit() returns true
# The normal and non-local-goto cases are handled here
exit(None, None, None)
Naturally, contextlib.contextmanager would then be modified to raise
SkipContext instead of RuntimeError if the generator doesn't yield. The
latter two examples would then correctly resume execution at the first
statement after the with block.
I don't see any other way to comprehensively fix the problem - without
it, there will always be some snippets of code which cannot correctly be
converted into context managers, and those snippets won't always be
obvious (e.g. the fact that combined() is potentially a broken context
manager implementation would surprise most people - it certainly
Thoughts? Do people hate the idea? Are there any backwards compatibility
problems that I'm missing? Should I write a PEP or just add the feature
to the with statement in 2.7/3.1?
Nick Coghlan | ncoghlan at gmail.com | Brisbane, Australia
More information about the Python-Dev