diff --git a/pep-0377.txt b/pep-0377.txt index 3d85686e5..05268a09a 100644 --- a/pep-0377.txt +++ b/pep-0377.txt @@ -129,40 +129,95 @@ then handles and suppresses:: # instead of as classes!) class CM(object): def __init__(self): - self.cmA = None - self.cmB = None + 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 + 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 + 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: - 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 + suppress = self.cmA.__exit__(*args): + return suppress + +With the proposed semantic change in place, the contextlib based examples +above would then "just work", but the class based version would need +adjustment to take advantage of the new semantics:: + + 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: + if self.cmA.__exit__(*sys.exc_info()): + # Suppress the exception, but don't run + # the body of the with statement either + raise SkipStatement + 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 + +There is currently a tentative suggestion [3] to add import-style syntax to +the ``with`` statement to allow multiple context managers to be included in +a single ``with`` statement without needing to use ``contextlib.nested``. In +that case the compiler has the option of simply emitting multiple ``with`` +statements at the AST level, thus allowing the semantics of actual nested +``with`` statements to be reproduced accurately. However, such a change +would highlight rather than alleviate the problem the current PEP aims to +address: it would not be possible to use ``contextlib.contextmanager``to +reliably factor out such ``with`` statements, as they would exhibit exactly +the same semantic differences as are seen with the ``combined()`` context +manager in the above example. Reference Implementation @@ -186,6 +241,9 @@ References .. [2] PEP 343: The "with" Statement (http://www.python.org/dev/peps/pep-0343/) +.. [3] Import-style syntax to reduce indentation of nested with statements + (http://mail.python.org/pipermail/python-ideas/2009-March/003188.html) + Copyright =========