X Tutup
Skip to content

Commit 0784a2e

Browse files
Jesse-Bakkerncoghlan
authored andcommitted
bpo-10049: Add a "no-op" (null) context manager to contextlib (GH-4464)
Adds a simpler and faster alternative to ExitStack for handling single optional context managers without having to change the lexical structure of your code.
1 parent 20d48a4 commit 0784a2e

File tree

4 files changed

+57
-19
lines changed

4 files changed

+57
-19
lines changed

Doc/library/contextlib.rst

Lines changed: 22 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,28 @@ Functions and classes provided:
137137
``page.close()`` will be called when the :keyword:`with` block is exited.
138138

139139

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+
140162
.. function:: suppress(*exceptions)
141163

142164
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
433455
context management protocol.
434456

435457

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-
454458
Catching exceptions from ``__enter__`` methods
455459
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
456460

Lib/contextlib.py

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
from collections import deque
66
from functools import wraps
77

8-
__all__ = ["asynccontextmanager", "contextmanager", "closing",
8+
__all__ = ["asynccontextmanager", "contextmanager", "closing", "nullcontext",
99
"AbstractContextManager", "ContextDecorator", "ExitStack",
1010
"redirect_stdout", "redirect_stderr", "suppress"]
1111

@@ -469,3 +469,24 @@ def _fix_exception_context(new_exc, old_exc):
469469
exc_details[1].__context__ = fixed_ctx
470470
raise
471471
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

Lib/test/test_contextlib.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -252,6 +252,16 @@ def close(self):
252252
1 / 0
253253
self.assertEqual(state, [1])
254254

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+
255265
class FileContextTestCase(unittest.TestCase):
256266

257267
def testWithOpen(self):
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff 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.

0 commit comments

Comments
 (0)
X Tutup