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.
This commit is contained in:
Jesse-Bakker 2017-11-23 01:23:28 +01:00 committed by Nick Coghlan
parent 20d48a44a5
commit 0784a2e5b1
4 changed files with 57 additions and 19 deletions

View 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
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

View file

@ -5,7 +5,7 @@
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 @@ def _fix_exception_context(new_exc, old_exc):
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

View file

@ -252,6 +252,16 @@ def close(self):
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):

View file

@ -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.