Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 22 additions & 18 deletions Doc/library/contextlib.rst
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,28 @@ Functions and classes provided:
``page.close()`` will be called when the :keyword:`with` block is exited.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Declare an explicit hyperlink anchor here with a blank line before the function definition:

.. _simplifying-support-for-single-optional-context-managers:

.. function:: nullcontext(enter_result=None)


.. _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
Expand Down Expand Up @@ -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
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if the section heading should be kept but the text changed to direct to nullcontext, so that people following links can see the new helper.

Also, if this is the only place that explained that ExitStack can work as a no-op, I think a line about that should be kept.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it definitely should be kept.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree we still need the hyperlink target, but given the clear example in the function's documentation, I think we can still delete the recipe section.


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

Expand Down
23 changes: 22 additions & 1 deletion Lib/contextlib.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"]

Expand Down Expand Up @@ -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
10 changes: 10 additions & 0 deletions Lib/test/test_contextlib.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
Original file line number Diff line number Diff line change
@@ -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.