diff -r 3ab32f7add6e Doc/library/contextlib.rst --- a/Doc/library/contextlib.rst Fri Aug 19 18:59:15 2016 +0200 +++ b/Doc/library/contextlib.rst Sat Aug 20 17:02:00 2016 +0200 @@ -107,7 +107,7 @@ ``page.close()`` will be called when the :keyword:`with` block is exited. -.. function:: suppress(*exceptions) +.. function:: suppress(*exceptions, unless=None) Return a context manager that suppresses any of the specified exceptions if they occur in the body of a with statement and then resumes execution @@ -118,6 +118,14 @@ silently continuing with program execution is known to be the right thing to do. + A whitelist of exceptions that will not suppressed can also be specified + as one exception or a tuple of exceptions to the *unless* keyword + argument. + Any whitelisted exception will not be suppressed. + Such whitelisted exceptions should normally be subclasses of at least one + of the suppressed exceptions for this operation to make sense. But that + is not enforced. + For example:: from contextlib import suppress @@ -125,7 +133,7 @@ with suppress(FileNotFoundError): os.remove('somefile.tmp') - with suppress(FileNotFoundError): + with suppress(OSError, unless=FileNotFoundError): os.remove('someotherfile.tmp') This code is equivalent to:: @@ -138,12 +146,16 @@ try: os.remove('someotherfile.tmp') except FileNotFoundError: + raise + except OSError: pass This context manager is :ref:`reentrant `. .. versionadded:: 3.4 + .. versionchanged:: 3.6 + The *unless* keyword argument was added. .. function:: redirect_stdout(new_target) diff -r 3ab32f7add6e Lib/contextlib.py --- a/Lib/contextlib.py Fri Aug 19 18:59:15 2016 +0200 +++ b/Lib/contextlib.py Sat Aug 20 17:02:00 2016 +0200 @@ -234,10 +234,38 @@ with suppress(FileNotFoundError): os.remove(somefile) # Execution still resumes here if the file was already removed + + A whitelist of exceptions that will not suppressed can also be specified + as one exception or a tuple of exceptions to the 'unless' keyword + argument. + Any whitelisted exception will not be suppressed, even if it is a subclass + of one of the supplied exceptions to be suppressed. + Such whitelisted exceptions should normally be subclasses of at least one + of the suppressed exceptions for this operation to make sense. But that + is not enforced. + + try: + with suppress(OSError, unless=PermissionError): + os.remove(somefile) + # Execution still resumes here for any OSError, + # except for PermissionError. + except PermissionError: + # Execution resumes here for PermissionError. + pass + + try: + with suppress(OSError, unless=(PermissionError, FileNotFoundError)): + os.remove(somefile) + # Execution still resumes here for any OSError, + # except for PermissionError and FileNotFoundError. + except (PermissionError, FileNotFoundError): + # Execution resumes here for PermissionError and FileNotFoundError. + pass """ - def __init__(self, *exceptions): + def __init__(self, *exceptions, unless=None): self._exceptions = exceptions + self._unless = unless or () def __enter__(self): pass @@ -252,7 +280,9 @@ # exactly reproduce the limitations of the CPython interpreter. # # See http://bugs.python.org/issue12029 for more details - return exctype is not None and issubclass(exctype, self._exceptions) + return exctype is not None and \ + issubclass(exctype, self._exceptions) and \ + not issubclass(exctype, self._unless) # Inspired by discussions on http://bugs.python.org/issue13585 diff -r 3ab32f7add6e Lib/test/test_contextlib.py --- a/Lib/test/test_contextlib.py Fri Aug 19 18:59:15 2016 +0200 +++ b/Lib/test/test_contextlib.py Sat Aug 20 17:02:00 2016 +0200 @@ -953,5 +953,30 @@ 1/0 self.assertTrue(outer_continued) + def test_unless(self): + with self.assertRaises(ZeroDivisionError): + with suppress(Exception, unless=ZeroDivisionError): + 1/0 + with suppress(Exception, unless=ZeroDivisionError): + len(5) + with self.assertRaises(ZeroDivisionError): + with suppress(ArithmeticError, TypeError, unless=ZeroDivisionError): + 1/0 + with suppress(ArithmeticError, TypeError, unless=TypeError): + 1/0 + + def test_multiple_unless(self): + with self.assertRaises(ZeroDivisionError): + with suppress(Exception, unless=(ArithmeticError, TypeError)): + 1/0 + with self.assertRaises(TypeError): + with suppress(Exception, unless=(ArithmeticError, TypeError)): + len(5) + with suppress(Exception, unless=(IndexError, OSError)): + 1/0 + with suppress(Exception, unless=(IndexError, OSError)): + len(5) + + if __name__ == "__main__": unittest.main()