]> granicus.if.org Git - python/commitdiff
bpo-10049: Add a "no-op" (null) context manager to contextlib (GH-4464)
authorJesse-Bakker <jessebakker00@gmail.com>
Thu, 23 Nov 2017 00:23:28 +0000 (01:23 +0100)
committerNick Coghlan <ncoghlan@gmail.com>
Thu, 23 Nov 2017 00:23:28 +0000 (10:23 +1000)
Adds a simpler and faster alternative to ExitStack for handling
single optional context managers without having to change the
lexical structure of your code.

Doc/library/contextlib.rst
Lib/contextlib.py
Lib/test/test_contextlib.py
Misc/NEWS.d/next/Library/2017-11-22-17-21-01.bpo-10049.ttsBqb.rst [new file with mode: 0644]

index 19793693b7ba68c9b41c09028ccbd086a2240493..48ca0da6b95f3ab494207f104eed69f03291631e 100644 (file)
@@ -137,6 +137,28 @@ Functions and classes provided:
    ``page.close()`` will be called when the :keyword:`with` block is exited.
 
 
+.. _simplifying-support-for-single-optional-context-managers:
+
+.. function:: nullcontext(enter_result=None)
+
+   Return a context manager that returns enter_result from ``__enter__``, but
+   otherwise does nothing. It is intended to be used as a stand-in for an
+   optional context manager, for example::
+
+      def process_file(file_or_path):
+          if isinstance(file_or_path, str):
+              # If string, open file
+              cm = open(file_or_path)
+          else:
+              # Caller is responsible for closing file
+              cm = nullcontext(file_or_path)
+
+          with cm as file:
+              # Perform processing on the file
+
+   .. versionadded:: 3.7
+
+
 .. function:: suppress(*exceptions)
 
    Return a context manager that suppresses any of the specified exceptions
@@ -433,24 +455,6 @@ statements to manage arbitrary resources that don't natively support the
 context management protocol.
 
 
-Simplifying support for single optional context managers
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-In the specific case of a single optional context manager, :class:`ExitStack`
-instances can be used as a "do nothing" context manager, allowing a context
-manager to easily be omitted without affecting the overall structure of
-the source code::
-
-   def debug_trace(details):
-       if __debug__:
-           return TraceContext(details)
-       # Don't do anything special with the context in release mode
-       return ExitStack()
-
-   with debug_trace():
-       # Suite is traced in debug mode, but runs normally otherwise
-
-
 Catching exceptions from ``__enter__`` methods
 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
index 962cedab490eb226571ef89d40bb4b0aa8064671..c1f8a84617fce43e5b6af7de4387a81a2cb0b9ea 100644 (file)
@@ -5,7 +5,7 @@ import _collections_abc
 from collections import deque
 from functools import wraps
 
-__all__ = ["asynccontextmanager", "contextmanager", "closing",
+__all__ = ["asynccontextmanager", "contextmanager", "closing", "nullcontext",
            "AbstractContextManager", "ContextDecorator", "ExitStack",
            "redirect_stdout", "redirect_stderr", "suppress"]
 
@@ -469,3 +469,24 @@ class ExitStack(AbstractContextManager):
                 exc_details[1].__context__ = fixed_ctx
                 raise
         return received_exc and suppressed_exc
+
+
+class nullcontext(AbstractContextManager):
+    """Context manager that does no additional processing.
+
+    Used as a stand-in for a normal context manager, when a particular
+    block of code is only sometimes used with a normal context manager:
+
+    cm = optional_cm if condition else nullcontext()
+    with cm:
+        # Perform operation, using optional_cm if condition is True
+    """
+
+    def __init__(self, enter_result=None):
+        self.enter_result = enter_result
+
+    def __enter__(self):
+        return self.enter_result
+
+    def __exit__(self, *excinfo):
+        pass
index 64b6578ff94eb30954694dfda5ae02bda6f6d756..1a5e6edad9b28f22e14a22225b7d4f06a026b679 100644 (file)
@@ -252,6 +252,16 @@ class ClosingTestCase(unittest.TestCase):
                 1 / 0
         self.assertEqual(state, [1])
 
+
+class NullcontextTestCase(unittest.TestCase):
+    def test_nullcontext(self):
+        class C:
+            pass
+        c = C()
+        with nullcontext(c) as c_in:
+            self.assertIs(c_in, c)
+
+
 class FileContextTestCase(unittest.TestCase):
 
     def testWithOpen(self):
diff --git a/Misc/NEWS.d/next/Library/2017-11-22-17-21-01.bpo-10049.ttsBqb.rst b/Misc/NEWS.d/next/Library/2017-11-22-17-21-01.bpo-10049.ttsBqb.rst
new file mode 100644 (file)
index 0000000..b6153c2
--- /dev/null
@@ -0,0 +1,3 @@
+Added *nullcontext* no-op context manager to contextlib. This provides a
+simpler and faster alternative to ExitStack() when handling optional context
+managers.