From 3cc01aba0b1f4b9edc2f09e2ace650095437021d Mon Sep 17 00:00:00 2001 From: Jesse Bakker Date: Sun, 19 Nov 2017 18:40:20 +0100 Subject: [PATCH 1/9] Add nullcontext to contextlib --- Doc/library/contextlib.rst | 33 +++++++++++++++------------------ Lib/contextlib.py | 22 +++++++++++++++++++++- Lib/test/test_contextlib.py | 10 ++++++++++ 3 files changed, 46 insertions(+), 19 deletions(-) diff --git a/Doc/library/contextlib.rst b/Doc/library/contextlib.rst index 19793693b7ba68c..c64f3178fe138d6 100644 --- a/Doc/library/contextlib.rst +++ b/Doc/library/contextlib.rst @@ -137,6 +137,21 @@ Functions and classes provided: ``page.close()`` will be called when the :keyword:`with` block is exited. +.. function:: nullcontext(thing=None) + + Return a context manager that just returns *thing*. It is intended to be used + as a standin for an optional context manager, for example:: + + def debug_trace(details): + if __debug__: + return TraceContext(details) + # Don't do anything special with the context in release mode + return nullcontext(details) + + with debug_trace(details) as d: + # Suite is traced in debug mode, but runs normally otherwise + + .. function:: suppress(*exceptions) Return a context manager that suppresses any of the specified exceptions @@ -433,24 +448,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 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/Lib/contextlib.py b/Lib/contextlib.py index 962cedab490eb22..6ab1b33043f999c 100644 --- a/Lib/contextlib.py +++ b/Lib/contextlib.py @@ -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,23 @@ 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 standin 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, thing=None): + self.thing = thing + + def __enter__(self): + return self.thing + + def __exit__(self, *excinfo): + pass diff --git a/Lib/test/test_contextlib.py b/Lib/test/test_contextlib.py index 64b6578ff94eb30..1a5e6edad9b28f2 100644 --- a/Lib/test/test_contextlib.py +++ b/Lib/test/test_contextlib.py @@ -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): From ca4d97e9559a900bd8f54d12a0fb618e7edf0f80 Mon Sep 17 00:00:00 2001 From: Jesse Bakker Date: Sun, 19 Nov 2017 18:42:38 +0100 Subject: [PATCH 2/9] Add nullcontext to contextlib --- Doc/library/contextlib.rst | 2 +- Lib/contextlib.py | 4 ++-- .../next/Library/2017-11-19-18-42-31.bpo-10049.U91c0A.rst | 1 + 3 files changed, 4 insertions(+), 3 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2017-11-19-18-42-31.bpo-10049.U91c0A.rst diff --git a/Doc/library/contextlib.rst b/Doc/library/contextlib.rst index c64f3178fe138d6..aa000bf6fa9d7b3 100644 --- a/Doc/library/contextlib.rst +++ b/Doc/library/contextlib.rst @@ -139,7 +139,7 @@ Functions and classes provided: .. function:: nullcontext(thing=None) - Return a context manager that just returns *thing*. It is intended to be used + Return a context manager that just returns *thing*. It is intended to be used as a standin for an optional context manager, for example:: def debug_trace(details): diff --git a/Lib/contextlib.py b/Lib/contextlib.py index 6ab1b33043f999c..05540b8db17a993 100644 --- a/Lib/contextlib.py +++ b/Lib/contextlib.py @@ -475,7 +475,7 @@ class nullcontext(AbstractContextManager): """Context manager that does no additional processing. Used as a standin 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 @@ -483,7 +483,7 @@ class nullcontext(AbstractContextManager): def __init__(self, thing=None): self.thing = thing - + def __enter__(self): return self.thing diff --git a/Misc/NEWS.d/next/Library/2017-11-19-18-42-31.bpo-10049.U91c0A.rst b/Misc/NEWS.d/next/Library/2017-11-19-18-42-31.bpo-10049.U91c0A.rst new file mode 100644 index 000000000000000..7f862de85f3afd7 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2017-11-19-18-42-31.bpo-10049.U91c0A.rst @@ -0,0 +1 @@ +Added nullcontext no-op context manager to contextlib From a194b9cc9f9cfef04e857856c4c668a5f02022f1 Mon Sep 17 00:00:00 2001 From: Jesse Bakker Date: Sun, 19 Nov 2017 19:48:33 +0100 Subject: [PATCH 3/9] Docs typo and formatting --- Doc/library/contextlib.rst | 2 +- Lib/contextlib.py | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Doc/library/contextlib.rst b/Doc/library/contextlib.rst index aa000bf6fa9d7b3..2b0883f9e360a15 100644 --- a/Doc/library/contextlib.rst +++ b/Doc/library/contextlib.rst @@ -140,7 +140,7 @@ Functions and classes provided: .. function:: nullcontext(thing=None) Return a context manager that just returns *thing*. It is intended to be used - as a standin for an optional context manager, for example:: + as a stand-in for an optional context manager, for example:: def debug_trace(details): if __debug__: diff --git a/Lib/contextlib.py b/Lib/contextlib.py index 05540b8db17a993..b64e1074bd3157d 100644 --- a/Lib/contextlib.py +++ b/Lib/contextlib.py @@ -474,7 +474,8 @@ def _fix_exception_context(new_exc, old_exc): class nullcontext(AbstractContextManager): """Context manager that does no additional processing. - Used as a standin for a normal context manager, when a particular block of code is only sometimes used with a normal context manager: + 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: From 23cc840658c2e91e907cb7bcc423e020b2877614 Mon Sep 17 00:00:00 2001 From: Jesse Bakker Date: Sun, 19 Nov 2017 19:57:25 +0100 Subject: [PATCH 4/9] Fix whitespace --- Lib/contextlib.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/contextlib.py b/Lib/contextlib.py index b64e1074bd3157d..011012700235a20 100644 --- a/Lib/contextlib.py +++ b/Lib/contextlib.py @@ -474,7 +474,7 @@ def _fix_exception_context(new_exc, old_exc): class nullcontext(AbstractContextManager): """Context manager that does no additional processing. - Used as a stand-in for a normal context manager, when a particular + 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() From 937342205728bd7691e9947ccb4699e5a7deb509 Mon Sep 17 00:00:00 2001 From: Jesse Bakker Date: Sun, 19 Nov 2017 21:34:02 +0100 Subject: [PATCH 5/9] Change example in docs to a more common/intuitive one --- Doc/library/contextlib.rst | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/Doc/library/contextlib.rst b/Doc/library/contextlib.rst index 2b0883f9e360a15..bb276e4bf552517 100644 --- a/Doc/library/contextlib.rst +++ b/Doc/library/contextlib.rst @@ -142,14 +142,16 @@ Functions and classes provided: Return a context manager that just returns *thing*. It is intended to be used as a stand-in for an optional context manager, for example:: - def debug_trace(details): - if __debug__: - return TraceContext(details) - # Don't do anything special with the context in release mode - return nullcontext(details) - - with debug_trace(details) as d: - # Suite is traced in debug mode, but runs normally otherwise + 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 .. function:: suppress(*exceptions) From 7e6f26e53642c315d88a517718aaa4ba4ff24262 Mon Sep 17 00:00:00 2001 From: Jesse Bakker Date: Wed, 22 Nov 2017 17:30:27 +0100 Subject: [PATCH 6/9] Edited lib and docs according to code review by @ncoghlan --- Doc/library/contextlib.rst | 11 ++++++++--- Lib/contextlib.py | 6 +++--- .../Library/2017-11-19-18-42-31.bpo-10049.U91c0A.rst | 1 - .../Library/2017-11-22-17-21-01.bpo-10049.ttsBqb.rst | 3 +++ 4 files changed, 14 insertions(+), 7 deletions(-) delete mode 100644 Misc/NEWS.d/next/Library/2017-11-19-18-42-31.bpo-10049.U91c0A.rst create mode 100644 Misc/NEWS.d/next/Library/2017-11-22-17-21-01.bpo-10049.ttsBqb.rst diff --git a/Doc/library/contextlib.rst b/Doc/library/contextlib.rst index bb276e4bf552517..3805d36f9946594 100644 --- a/Doc/library/contextlib.rst +++ b/Doc/library/contextlib.rst @@ -137,10 +137,13 @@ Functions and classes provided: ``page.close()`` will be called when the :keyword:`with` block is exited. -.. function:: nullcontext(thing=None) +.. _simplifying-support-for-single-optional-context-managers: - Return a context manager that just returns *thing*. It is intended to be used - as a stand-in for an optional context manager, for example:: +.. 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): @@ -153,6 +156,8 @@ Functions and classes provided: with cm as file: # Perform processing on the file + .. versionadded: 3.7 + .. function:: suppress(*exceptions) diff --git a/Lib/contextlib.py b/Lib/contextlib.py index 011012700235a20..c1f8a84617fce43 100644 --- a/Lib/contextlib.py +++ b/Lib/contextlib.py @@ -482,11 +482,11 @@ class nullcontext(AbstractContextManager): # Perform operation, using optional_cm if condition is True """ - def __init__(self, thing=None): - self.thing = thing + def __init__(self, enter_result=None): + self.enter_result = enter_result def __enter__(self): - return self.thing + return self.enter_result def __exit__(self, *excinfo): pass diff --git a/Misc/NEWS.d/next/Library/2017-11-19-18-42-31.bpo-10049.U91c0A.rst b/Misc/NEWS.d/next/Library/2017-11-19-18-42-31.bpo-10049.U91c0A.rst deleted file mode 100644 index 7f862de85f3afd7..000000000000000 --- a/Misc/NEWS.d/next/Library/2017-11-19-18-42-31.bpo-10049.U91c0A.rst +++ /dev/null @@ -1 +0,0 @@ -Added nullcontext no-op context manager to contextlib diff --git a/Misc/NEWS.d/next/Library/2017-11-22-17-21-01.bpo-10049.ttsBqb.rst b/Misc/NEWS.d/next/Library/2017-11-22-17-21-01.bpo-10049.ttsBqb.rst new file mode 100644 index 000000000000000..b6153c235d0cc48 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2017-11-22-17-21-01.bpo-10049.ttsBqb.rst @@ -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. From dec53046251f7ca37c005ccb1c1ddcc8ce11954a Mon Sep 17 00:00:00 2001 From: Jesse Bakker Date: Wed, 22 Nov 2017 18:15:40 +0100 Subject: [PATCH 7/9] Fixed docs indentation --- Doc/library/contextlib.rst | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/Doc/library/contextlib.rst b/Doc/library/contextlib.rst index 3805d36f9946594..e7c10c5f84a2317 100644 --- a/Doc/library/contextlib.rst +++ b/Doc/library/contextlib.rst @@ -141,22 +141,22 @@ Functions and classes provided: .. 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 + 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) From 89ffdefebd42a46639c7ddf225e9e1f97503a350 Mon Sep 17 00:00:00 2001 From: Jesse Bakker Date: Wed, 22 Nov 2017 18:16:37 +0100 Subject: [PATCH 8/9] Fix docs indentation --- Doc/library/contextlib.rst | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/Doc/library/contextlib.rst b/Doc/library/contextlib.rst index e7c10c5f84a2317..0bc5e7876410787 100644 --- a/Doc/library/contextlib.rst +++ b/Doc/library/contextlib.rst @@ -145,16 +145,16 @@ Functions and classes provided: 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 + 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 From 9947ded7ea605cab79ef69f6a7bd61f2fe685796 Mon Sep 17 00:00:00 2001 From: Jesse Bakker Date: Thu, 23 Nov 2017 00:57:17 +0100 Subject: [PATCH 9/9] Fix versionadded directive --- Doc/library/contextlib.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/library/contextlib.rst b/Doc/library/contextlib.rst index 0bc5e7876410787..48ca0da6b95f3ab 100644 --- a/Doc/library/contextlib.rst +++ b/Doc/library/contextlib.rst @@ -156,7 +156,7 @@ Functions and classes provided: with cm as file: # Perform processing on the file - .. versionadded: 3.7 + .. versionadded:: 3.7 .. function:: suppress(*exceptions)