[Python-Dev] Allow __enter__() methods to skip the with statement body?

Brett Cannon brett at python.org
Wed Feb 25 19:43:38 CET 2009


On Wed, Feb 25, 2009 at 04:24, Nick Coghlan <ncoghlan at gmail.com> wrote:

> An interesting discrepancy [1] 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):
>
>  with cmA():
>    with cmB():
>      do_stuff()
>  # This will resume here without executing "Do stuff"
>
>  @contextlib.contextmanager
>  def combined():
>    with cmA():
>      with cmB():
>        yield
>
>  with combined():
>    do_stuff()
>  # This will raise RuntimeError complaining that the underlying
>  # generator didn't yield
>
>  with contextlib.nested(cmA(), cmB()):
>    do_stuff()
>  # 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 [2] to skip the body of the statement if __enter__()
> throws that specific exception:
>
>  mgr = (EXPR)
>  exit = mgr.__exit__  # Not calling it yet
>  try:
>    value = mgr.__enter__()
>  except SkipContext:
>    pass # This exception handler is the new part...
>  else:
>    exc = True
>    try:
>      VAR = value  # Only if "as VAR" is present
>      BLOCK
>    except:
>      # The exceptional case is handled here
>      exc = False
>      if not exit(*sys.exc_info()):
>        raise
>      # The exception is swallowed if exit() returns true
>    finally:
>      # The normal and non-local-goto cases are handled here
>      if exc:
>        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
> surprised me).
>
> Thoughts? Do people hate the idea?


No, but I do wonder how useful this truly is.


> Are there any backwards compatibility
> problems that I'm missing?


As long as the exception inherits from BaseException, no.


> Should I write a PEP or just add the feature
> to the with statement in 2.7/3.1?
>

Sounds PEPpy to me since you are proposing changing the semantics for a
syntactic construct.
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.python.org/pipermail/python-dev/attachments/20090225/bf0c729b/attachment.htm>


More information about the Python-Dev mailing list