[Python-checkins] r70237 - peps/trunk/pep-0377.txt
nick.coghlan
python-checkins at python.org
Sun Mar 8 04:52:42 CET 2009
Author: nick.coghlan
Date: Sun Mar 8 04:52:41 2009
New Revision: 70237
Log:
New PEP to cover problems with being to implement contextlib.nested() properly
Added:
peps/trunk/pep-0377.txt (contents, props changed)
Added: peps/trunk/pep-0377.txt
==============================================================================
--- (empty file)
+++ peps/trunk/pep-0377.txt Sun Mar 8 04:52:41 2009
@@ -0,0 +1,199 @@
+PEP: 377
+Title: Allow __enter__() methods to skip the statement body
+Version: $Revision$
+Last-Modified: $Date$
+Author: Nick Coghlan <ncoghlan at gmail.com>
+Status: Draft
+Type: Standards Track
+Content-Type: text/x-rst
+Created: 8-Mar-2009
+Python-Version: 2.7, 3.1
+Post-History: 8-Mar-2009
+
+
+Abstract
+========
+
+This PEP proposes a backwards compatible mechanism that allows ``__enter__()``
+methods to skip the body of the associated ``with`` statment. The lack of
+this ability currently means the ``contextlib.nested`` context manager
+is unable to fulfil its specification of being equivalent to writing out
+multiple nested ``with`` statements [1].
+
+The proposed change is to introduce a new flow control exception
+``SkipStatement``, and skip the execution of the ``with``
+statement body if ``__enter__()`` raises this exception.
+
+
+Proposed Change
+===============
+
+The semantics of the ``with`` statement will be changed to include a
+new ``try``/``except``/``else`` block around the call to ``__enter__()``.
+If ``SkipStatement`` is raised by the ``__enter__()`` method, then
+the main section of the ``with`` statement (now located in the ``else``
+clause) will not be executed. To avoid leaving the names in any ``as``
+clause unbound in this case, a new ``StatementSkipped`` singleton
+(similar to the existing ``NotImplemented`` singleton) will be
+assigned to all names that appear in the ``as`` clause.
+
+The components of the ``with`` statement remain as described in PEP 343 [2]::
+
+ with EXPR as VAR:
+ BLOCK
+
+After the modification, the ``with`` statement semantics would
+be as follows::
+
+ mgr = (EXPR)
+ exit = mgr.__exit__ # Not calling it yet
+ try:
+ value = mgr.__enter__()
+ except SkipStatement:
+ VAR = StatementSkipped
+ # Only if "as VAR" is present and
+ # VAR is a single name
+ # If VAR is a tuple of names, then StatementSkipped
+ # will be assigned to each name in the tuple
+ else:
+ exc = True
+ try:
+ 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)
+
+With the above change in place for the ``with`` statement semantics,
+``contextlib.contextmanager()`` will then be modified to raise
+``SkipStatement`` instead of ``RuntimeError`` when the underlying
+generator doesn't yield.
+
+Rationale for Change
+====================
+
+Currently, some apparently innocuous context managers may raise
+``RuntimeError`` when executed. This occurs when the context
+manager's ``__enter__()`` method encounters a situation where
+the written out version of the code corresponding to the
+context manager would skip the code that is now the body
+of the ``with`` statement. Since the ``__enter__()`` method
+has no mechanism available to signal this to the interpreter,
+it is instead forced to raise an exception that not only
+skips the body of the ``with`` statement, but also jumps over
+all code until the nearest exception handler. This goes against
+one of the design goals of the ``with`` statement, which was to
+be able to factor out arbitrary common exception handling code
+into a single context manager by putting into a generator
+function and replacing the variant part of the code with a
+``yield`` statement.
+
+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 a RuntimeError complaining that the context
+ # manager's underlying generator didn't yield
+
+ with contextlib.nested(cmA(), cmB()):
+ do_stuff()
+ # This will raise the same RuntimeError as the contextmanager()
+ # example (unsurprising, given that the nested() implementation
+ # uses contextmanager())
+
+ # The following class based version shows that the issue isn't
+ # specific to contextlib.contextmanager() (it also shows how
+ # much simpler it is to write context managers as generators
+ # instead of as classes!)
+ class CM(object):
+ def __init__(self):
+ self.cmA = None
+ self.cmB = None
+
+ def __enter__(self):
+ if self.cmA is not None:
+ raise RuntimeError("Can't re-use this CM")
+ self.cmA = cmA()
+ self.cmA.__enter__()
+ try:
+ self.cmB = cmB()
+ self.cmB.__enter__()
+ except:
+ self.cmA.__exit__(*sys.exc_info())
+ # Can't suppress in __enter__(), so must raise
+ raise
+
+ def __exit__(self, *args):
+ suppress = False
+ try:
+ if self.cmB is not None:
+ suppress = self.cmB.__exit__(*args)
+ except:
+ suppress = self.cmA.__exit__(*sys.exc_info()):
+ if not suppress:
+ # Exception has changed, so reraise explicitly
+ raise
+ else:
+ if suppress:
+ # cmB already suppressed the exception,
+ # so don't pass it to cmA
+ suppress = self.cmA.__exit__(None, None, None):
+ else:
+ suppress = self.cmA.__exit__(*args):
+ return suppress
+
+
+Reference Implementation
+========================
+
+In work.
+
+
+Acknowledgements
+================
+
+James William Pye both raised the issue and suggested the solution
+described in this PEP.
+
+References
+==========
+
+.. [1] Issue 5251: contextlib.nested inconsistent with nested with statements
+ (http://bugs.python.org/issue5251)
+
+.. [2] PEP 343: The "with" Statement
+ (http://www.python.org/dev/peps/pep-0343/)
+
+Copyright
+=========
+
+This document has been placed in the public domain.
+
+..
+ Local Variables:
+ mode: indented-text
+ indent-tabs-mode: nil
+ sentence-end-double-space: t
+ fill-column: 70
+ End:
More information about the Python-checkins
mailing list