bpo-10049: Add a "no-op" (null) context manager to contextlib (GH-4464) · python/cpython@0784a2e (original) (raw)

4 files changed

lines changed

Original file line number Diff line number Diff line change
@@ -137,6 +137,28 @@ Functions and classes provided:
137 137 ``page.close()`` will be called when the :keyword:`with` block is exited.
138 138
139 139
140 +.. _simplifying-support-for-single-optional-context-managers:
141 +
142 +.. function:: nullcontext(enter_result=None)
143 +
144 + Return a context manager that returns enter_result from ``__enter__``, but
145 + otherwise does nothing. It is intended to be used as a stand-in for an
146 + optional context manager, for example::
147 +
148 + def process_file(file_or_path):
149 + if isinstance(file_or_path, str):
150 + # If string, open file
151 + cm = open(file_or_path)
152 + else:
153 + # Caller is responsible for closing file
154 + cm = nullcontext(file_or_path)
155 +
156 + with cm as file:
157 + # Perform processing on the file
158 +
159 + .. versionadded:: 3.7
160 +
161 +
140 162 .. function:: suppress(*exceptions)
141 163
142 164 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
433 455 context management protocol.
434 456
435 457
436 -Simplifying support for single optional context managers
437 -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
438 -
439 -In the specific case of a single optional context manager, :class:`ExitStack`
440 -instances can be used as a "do nothing" context manager, allowing a context
441 -manager to easily be omitted without affecting the overall structure of
442 -the source code::
443 -
444 - def debug_trace(details):
445 - if __debug__:
446 - return TraceContext(details)
447 - # Don't do anything special with the context in release mode
448 - return ExitStack()
449 -
450 - with debug_trace():
451 - # Suite is traced in debug mode, but runs normally otherwise
452 -
453 -
454 458 Catching exceptions from ``__enter__`` methods
455 459 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
456 460
Original file line number Diff line number Diff line change
@@ -5,7 +5,7 @@
5 5 from collections import deque
6 6 from functools import wraps
7 7
8 -__all__ = ["asynccontextmanager", "contextmanager", "closing",
8 +__all__ = ["asynccontextmanager", "contextmanager", "closing", "nullcontext",
9 9 "AbstractContextManager", "ContextDecorator", "ExitStack",
10 10 "redirect_stdout", "redirect_stderr", "suppress"]
11 11
@@ -469,3 +469,24 @@ def _fix_exception_context(new_exc, old_exc):
469 469 exc_details[1].__context__ = fixed_ctx
470 470 raise
471 471 return received_exc and suppressed_exc
472 +
473 +
474 +class nullcontext(AbstractContextManager):
475 +"""Context manager that does no additional processing.
476 +
477 + Used as a stand-in for a normal context manager, when a particular
478 + block of code is only sometimes used with a normal context manager:
479 +
480 + cm = optional_cm if condition else nullcontext()
481 + with cm:
482 + # Perform operation, using optional_cm if condition is True
483 + """
484 +
485 +def __init__(self, enter_result=None):
486 +self.enter_result = enter_result
487 +
488 +def __enter__(self):
489 +return self.enter_result
490 +
491 +def __exit__(self, *excinfo):
492 +pass
Original file line number Diff line number Diff line change
@@ -252,6 +252,16 @@ def close(self):
252 252 1 / 0
253 253 self.assertEqual(state, [1])
254 254
255 +
256 +class NullcontextTestCase(unittest.TestCase):
257 +def test_nullcontext(self):
258 +class C:
259 +pass
260 +c = C()
261 +with nullcontext(c) as c_in:
262 +self.assertIs(c_in, c)
263 +
264 +
255 265 class FileContextTestCase(unittest.TestCase):
256 266
257 267 def testWithOpen(self):
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
1 +Added *nullcontext* no-op context manager to contextlib. This provides a
2 +simpler and faster alternative to ExitStack() when handling optional context
3 +managers.