diff options
author | Nick Coghlan <ncoghlan@gmail.com> | 2013-10-19 14:30:51 (GMT) |
---|---|---|
committer | Nick Coghlan <ncoghlan@gmail.com> | 2013-10-19 14:30:51 (GMT) |
commit | 8608d26e815a63f5a35524abea40ad80a5e93bb2 (patch) | |
tree | 1f7d7ccd3b19dd867ab67f7f37b7342092c0ed3f /Lib/contextlib.py | |
parent | e723622775172a2516f43721d998aae95f32e59d (diff) | |
download | cpython-8608d26e815a63f5a35524abea40ad80a5e93bb2.zip cpython-8608d26e815a63f5a35524abea40ad80a5e93bb2.tar.gz cpython-8608d26e815a63f5a35524abea40ad80a5e93bb2.tar.bz2 |
contextlib doc updates and refactoring
- explain single use, reusable and reentrant in docs
- converted suppress to a reentrant class based impl
- converted redirect_stdout to a reusable impl
- moved both suppress and redirect_stdout behind a functional
facade
- added reentrancy tests for the updated suppress
- added reusability tests for the updated redirect_stdio
- slightly cleaned up an exception from contextmanager
Diffstat (limited to 'Lib/contextlib.py')
-rw-r--r-- | Lib/contextlib.py | 81 |
1 files changed, 52 insertions, 29 deletions
diff --git a/Lib/contextlib.py b/Lib/contextlib.py index 144d6bb..a564943 100644 --- a/Lib/contextlib.py +++ b/Lib/contextlib.py @@ -48,7 +48,7 @@ class _GeneratorContextManager(ContextDecorator): try: return next(self.gen) except StopIteration: - raise RuntimeError("generator didn't yield") + raise RuntimeError("generator didn't yield") from None def __exit__(self, type, value, traceback): if type is None: @@ -117,6 +117,9 @@ def contextmanager(func): return helper +# Unfortunately, this was originally published as a class, so +# backwards compatibility prevents the use of the wrapper function +# approach used for the other classes class closing(object): """Context to automatically close something at the end of a block. @@ -141,55 +144,75 @@ class closing(object): def __exit__(self, *exc_info): self.thing.close() -class redirect_stdout: +class _RedirectStdout: + """Helper for redirect_stdout.""" + + def __init__(self, new_target): + self._new_target = new_target + self._old_target = self._sentinel = object() + + def __enter__(self): + if self._old_target is not self._sentinel: + raise RuntimeError("Cannot reenter {!r}".format(self)) + self._old_target = sys.stdout + sys.stdout = self._new_target + return self._new_target + + def __exit__(self, exctype, excinst, exctb): + restore_stdout = self._old_target + self._old_target = self._sentinel + sys.stdout = restore_stdout + +# Use a wrapper function since we don't care about supporting inheritance +# and a function gives much cleaner output in help() +def redirect_stdout(target): """Context manager for temporarily redirecting stdout to another file # How to send help() to stderr - with redirect_stdout(sys.stderr): help(dir) # How to write help() to a file - with open('help.txt', 'w') as f: with redirect_stdout(f): help(pow) - - # How to capture disassembly to a string - - import dis - import io - - f = io.StringIO() - with redirect_stdout(f): - dis.dis('x**2 - y**2') - s = f.getvalue() - """ + return _RedirectStdout(target) - def __init__(self, new_target): - self.new_target = new_target + +class _SuppressExceptions: + """Helper for suppress.""" + def __init__(self, *exceptions): + self._exceptions = exceptions def __enter__(self): - self.old_target = sys.stdout - sys.stdout = self.new_target - return self.new_target + pass def __exit__(self, exctype, excinst, exctb): - sys.stdout = self.old_target - -@contextmanager + # Unlike isinstance and issubclass, exception handling only + # looks at the concrete type heirarchy (ignoring the instance + # and subclass checking hooks). However, all exceptions are + # also required to be concrete subclasses of BaseException, so + # if there's a discrepancy in behaviour, we currently consider it + # the fault of the strange way the exception has been defined rather + # than the fact that issubclass can be customised while the + # exception checks can't. + # See http://bugs.python.org/issue12029 for more details + return exctype is not None and issubclass(exctype, self._exceptions) + +# Use a wrapper function since we don't care about supporting inheritance +# and a function gives much cleaner output in help() def suppress(*exceptions): """Context manager to suppress specified exceptions - with suppress(OSError): - os.remove(somefile) + 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 """ - try: - yield - except exceptions: - pass + return _SuppressExceptions(*exceptions) # Inspired by discussions on http://bugs.python.org/issue13585 class ExitStack(object): |