Repository: numpy/numpy
Pull request: ENH: add `np.printoptions`, a context manager
Just a syntax sugar over the `get_printoptions/set_printoptions` pair:

```
>>> import numpy as np
>>> with np.printoptions(precision=2):
...     print(np.array([2.0]) / 3)
... 
[0.67]
>>> print(np.array([2.0]) / 3)
[0.66666667]
```


Something like this is probably present in everyone's personal toolbox. One can argue this is where this belongs: personal toolboxes and SO answers (there's at least one, likely more).

The argument for having it in numpy is mostly convenience and ease of introducing to newbees: I can see this used fairly early in teaching, especially non-CS students; the implementation is a bit more magical even if is a mere half dozen lines.

EDIT: closes gh-8344

Problem statement:
The method to undo numpy set_printoptions
In order to undo what ever print options I have set I need to call a function with a massive number of arguments. Why isn't there an argument like default=False and then if I call it with default=True I get back to the original print options. This doesn't have much of an impact in regular programs but it does have a huge impact in jupyter notebooks! A huge amount of computational physics teaching is shifting to these and it is so frustrating to have to have such an ugly line of code for absolutely no reason when it could be fixed in the simplest way.  

This is taken directly from numpys documentation 
np.set_printoptions(edgeitems=3,infstr='inf',
... linewidth=75, nanstr='nan', precision=8,
... suppress=False, threshold=1000, formatter=None)

Hints from issue discussion:
There has been a proposal for a printoptions context manager, see #3987. Man, that is already three years old...

Commit message: ENH: add `np.printoptions`, a context manager

Just a syntax sugar over the get_printoptions/set_printoptions pair.

Patch to review:
diff --git a/numpy/core/arrayprint.py b/numpy/core/arrayprint.py
index 6af73e6d7355..d6e3cfda3805 100644
--- a/numpy/core/arrayprint.py
+++ b/numpy/core/arrayprint.py
@@ -6,8 +6,8 @@
 from __future__ import division, absolute_import, print_function
 
 __all__ = ["array2string", "array_str", "array_repr", "set_string_function",
-           "set_printoptions", "get_printoptions", "format_float_positional",
-           "format_float_scientific"]
+           "set_printoptions", "get_printoptions", "printoptions",
+           "format_float_positional", "format_float_scientific"]
 __docformat__ = 'restructuredtext'
 
 #
@@ -49,7 +49,7 @@
 from .numerictypes import (longlong, intc, int_, float_, complex_, bool_,
                            flexible)
 import warnings
-
+import contextlib
 
 _format_options = {
     'edgeitems': 3,  # repr N leading and trailing items of each dimension
@@ -273,6 +273,40 @@ def get_printoptions():
     return _format_options.copy()
 
 
+@contextlib.contextmanager
+def printoptions(*args, **kwds):
+    """Context manager for setting print options.
+
+    See `set_printoptions` for the full description of available options.
+
+    Examples
+    --------
+
+    `printoptions` sets print options temporarily, and restores them back
+    at the end of the `with`-block:
+
+    >>> with np.printoptions(precision=2):
+    ...     print(np.array([2.0])) / 3
+    [0.67]
+
+    The `as`-clause of the `with`-statement gives the current print options:
+
+    >>> with np.printoptions(precision=2) as opts:
+    ...      assert_equal(opts, np.get_printoptions())
+
+    See Also
+    --------
+    set_printoptions, get_printoptions
+
+    """
+    opts = np.get_printoptions()
+    try:
+        np.set_printoptions(*args, **kwds)
+        yield np.get_printoptions()
+    finally:
+        np.set_printoptions(**opts)
+
+
 def _leading_trailing(a, edgeitems, index=()):
     """
     Keep only the N-D corners (leading and trailing edges) of an array.
diff --git a/numpy/core/tests/test_arrayprint.py b/numpy/core/tests/test_arrayprint.py
index 55fb226cc626..97b1bd4da90d 100644
--- a/numpy/core/tests/test_arrayprint.py
+++ b/numpy/core/tests/test_arrayprint.py
@@ -721,5 +721,37 @@ def test_unicode_object_array():
     assert_equal(repr(x), expected)
 
 
+class TestContextManager(object):
+    def test_ctx_mgr(self):
+        # test that context manager actuall works
+        with np.printoptions(precision=2):
+            s = str(np.array([2.0]) / 3)
+        assert_equal(s, '[0.67]')
+
+    def test_ctx_mgr_restores(self):
+        # test that print options are actually restrored
+        opts = np.get_printoptions()
+        with np.printoptions(precision=opts['precision'] - 1,
+                             linewidth=opts['linewidth'] - 4):
+            pass
+        assert_equal(np.get_printoptions(), opts)
+
+    def test_ctx_mgr_exceptions(self):
+        # test that print options are restored even if an exeption is raised
+        opts = np.get_printoptions()
+        try:
+            with np.printoptions(precision=2, linewidth=11):
+                raise ValueError
+        except ValueError:
+            pass
+        assert_equal(np.get_printoptions(), opts)
+
+    def test_ctx_mgr_as_smth(self):
+        opts = {"precision": 2}
+        with np.printoptions(**opts) as ctx:
+            saved_opts = ctx.copy()
+        assert_equal({k: saved_opts[k] for k in opts}, opts)
+
+
 if __name__ == "__main__":
     run_module_suite()


Code review comments:
diff_hunk: @@ -273,6 +273,40 @@ def get_printoptions():
     return _format_options.copy()
 
 
+@contextlib.contextmanager
+def printoptions(*args, **kwds):
path: numpy/core/arrayprint.py
text: @user1:
nit: `**kwargs` is a more common spelling