Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

bpo-10049: Add a "no-op" (null) context manager to contextlib #4464

Merged
merged 9 commits into from Nov 23, 2017
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
40 changes: 22 additions & 18 deletions Doc/library/contextlib.rst
Expand Up @@ -137,6 +137,28 @@ Functions and classes provided:
``page.close()`` will be called when the :keyword:`with` block is exited.

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