diff -r 414332e55f6c Lib/contextlib.py --- a/Lib/contextlib.py Sat Nov 22 16:03:40 2014 -0800 +++ b/Lib/contextlib.py Sun Nov 23 15:46:26 2014 +0000 @@ -4,7 +4,7 @@ from functools import wraps from warnings import warn -__all__ = ["contextmanager", "nested", "closing"] +__all__ = ["contextmanager", "nested", "closing", "suppress"] class GeneratorContextManager(object): """Helper for @contextmanager decorator.""" @@ -152,3 +152,33 @@ return self.thing def __exit__(self, *exc_info): self.thing.close() + + +class suppress: + """Context manager to suppress specified exceptions + + After the exception is suppressed, execution proceeds with the next + statement following the with statement. + + with suppress(FileNotFoundError): + os.remove(somefile) + # Execution still resumes here if the file was already removed + """ + + def __init__(self, *exceptions): + self._exceptions = exceptions + + def __enter__(self): + pass + + def __exit__(self, exctype, excinst, exctb): + # Unlike isinstance and issubclass, CPython exception handling + # currently only looks at the concrete type hierarchy (ignoring + # the instance and subclass checking hooks). While Guido considers + # that a bug rather than a feature, it's a fairly hard one to fix + # due to various internal implementation details. suppress provides + # the simpler issubclass based semantics, rather than trying to + # 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) diff -r 414332e55f6c Lib/test/test_contextlib.py --- a/Lib/test/test_contextlib.py Sat Nov 22 16:03:40 2014 -0800 +++ b/Lib/test/test_contextlib.py Sun Nov 23 15:46:26 2014 +0000 @@ -315,6 +315,58 @@ return True self.boilerPlate(lock, locked) + +class TestSuppress(unittest.TestCase): + + def test_instance_docs(self): + # Issue 19330: ensure context manager instances have good docstrings + cm_docstring = suppress.__doc__ + obj = suppress() + self.assertEqual(obj.__doc__, cm_docstring) + + def test_no_result_from_enter(self): + with suppress(ValueError) as enter_result: + self.assertIsNone(enter_result) + + def test_no_exception(self): + with suppress(ValueError): + self.assertEqual(pow(2, 5), 32) + + def test_exact_exception(self): + with suppress(TypeError): + len(5) + + def test_exception_hierarchy(self): + with suppress(LookupError): + 'Hello'[50] + + def test_other_exception(self): + with self.assertRaises(ZeroDivisionError): + with suppress(TypeError): + 1/0 + + def test_no_args(self): + with self.assertRaises(ZeroDivisionError): + with suppress(): + 1/0 + + def test_multiple_exception_args(self): + with suppress(ZeroDivisionError, TypeError): + 1/0 + with suppress(ZeroDivisionError, TypeError): + len(5) + + def test_cm_is_reentrant(self): + ignore_exceptions = suppress(Exception) + with ignore_exceptions: + pass + with ignore_exceptions: + len(5) + with ignore_exceptions: + 1/0 + with ignore_exceptions: # Check nested usage + len(5) + # This is needed to make the test actually run under regrtest.py! def test_main(): with test_support.check_warnings(("With-statements now directly support "