diff --git a/doc/users/next_whats_new/2018-09-19-AL.rst b/doc/users/next_whats_new/2018-09-19-AL.rst
new file mode 100644
index 000000000000..77240958aa90
--- /dev/null
+++ b/doc/users/next_whats_new/2018-09-19-AL.rst
@@ -0,0 +1,11 @@
+:orphan:
+
+`matplotlib.rc_context` is now a `contextlib.contextmanager`
+````````````````````````````````````````````````````````````
+
+`matplotlib.rc_context` can now be used as a decorator (technically, it is now
+implemented as a `contextlib.contextmanager`), e.g. ::
+
+ @rc_context({"lines.linewidth": 2})
+ def some_function(...):
+ ...
diff --git a/doc/users/prev_whats_new/whats_new_1.3.rst b/doc/users/prev_whats_new/whats_new_1.3.rst
index e5897667e7fa..e57e35d230f5 100644
--- a/doc/users/prev_whats_new/whats_new_1.3.rst
+++ b/doc/users/prev_whats_new/whats_new_1.3.rst
@@ -86,7 +86,7 @@ New plotting features
To give your plots a sense of authority that they may be missing,
Michael Droettboom (inspired by the work of many others in
:ghpull:`1329`) has added an `xkcd-style `__ sketch
-plotting mode. To use it, simply call :func:`matplotlib.pyplot.xkcd`
+plotting mode. To use it, simply call `matplotlib.pyplot.xkcd`
before creating your plot. For really fine control, it is also possible
to modify each artist's sketch parameters individually with
:meth:`matplotlib.artist.Artist.set_sketch_params`.
diff --git a/lib/matplotlib/__init__.py b/lib/matplotlib/__init__.py
index 9486610da69f..8e9cae85a1de 100644
--- a/lib/matplotlib/__init__.py
+++ b/lib/matplotlib/__init__.py
@@ -1035,7 +1035,8 @@ def rc_file(fname, *, use_default_template=True):
if k not in STYLE_BLACKLIST})
-class rc_context:
+@contextlib.contextmanager
+def rc_context(rc=None, fname=None):
"""
Return a context manager for managing rc settings.
@@ -1052,49 +1053,24 @@ class rc_context:
with mpl.rc_context(rc={'text.usetex': True}, fname='screen.rc'):
plt.plot(x, a)
- The 'rc' dictionary takes precedence over the settings loaded from
- 'fname'. Passing a dictionary only is also valid. For example a
- common usage is::
+ The *rc* dictionary takes precedence over the settings loaded from *fname*.
+ Passing a dictionary only is also valid. For example, a common usage is::
- with mpl.rc_context(rc={'interactive': False}):
+ with mpl.rc_context({'interactive': False}):
fig, ax = plt.subplots()
ax.plot(range(3), range(3))
fig.savefig('A.png', format='png')
plt.close(fig)
"""
- # While it may seem natural to implement rc_context using
- # contextlib.contextmanager, that would entail always calling the finally:
- # clause of the contextmanager (which restores the original rcs) including
- # during garbage collection; as a result, something like `plt.xkcd();
- # gc.collect()` would result in the style being lost (as `xkcd()` is
- # implemented on top of rc_context, and nothing is holding onto context
- # manager except possibly circular references.
-
- def __init__(self, rc=None, fname=None):
- self._orig = rcParams.copy()
- try:
- if fname:
- rc_file(fname)
- if rc:
- rcParams.update(rc)
- except Exception:
- self.__fallback()
- raise
-
- def __fallback(self):
- # If anything goes wrong, revert to the original rcs.
- updated_backend = self._orig['backend']
- dict.update(rcParams, self._orig)
- # except for the backend. If the context block triggered resolving
- # the auto backend resolution keep that value around
- if self._orig['backend'] is rcsetup._auto_backend_sentinel:
- rcParams['backend'] = updated_backend
-
- def __enter__(self):
- return self
-
- def __exit__(self, exc_type, exc_value, exc_tb):
- self.__fallback()
+ orig = rcParams.copy()
+ try:
+ if fname:
+ rc_file(fname)
+ if rc:
+ rcParams.update(rc)
+ yield
+ finally:
+ dict.update(rcParams, orig) # Revert to the original rcs.
def use(backend, *, force=True):
diff --git a/lib/matplotlib/pyplot.py b/lib/matplotlib/pyplot.py
index c98e7c3cef18..d8aaabbe32ad 100644
--- a/lib/matplotlib/pyplot.py
+++ b/lib/matplotlib/pyplot.py
@@ -408,12 +408,11 @@ def setp(obj, *args, **kwargs):
def xkcd(scale=1, length=100, randomness=2):
"""
- Turn on `xkcd `_ sketch-style drawing mode.
- This will only have effect on things drawn after this function is
- called.
+ Turn on `xkcd `_ sketch-style drawing mode. This will
+ only have effect on things drawn after this function is called.
For best results, the "Humor Sans" font should be installed: it is
- not included with matplotlib.
+ not included with Matplotlib.
Parameters
----------
@@ -440,29 +439,46 @@ def xkcd(scale=1, length=100, randomness=2):
# This figure will be in regular style
fig2 = plt.figure()
"""
- if rcParams['text.usetex']:
- raise RuntimeError(
- "xkcd mode is not compatible with text.usetex = True")
-
- from matplotlib import patheffects
- return rc_context({
- 'font.family': ['xkcd', 'xkcd Script', 'Humor Sans', 'Comic Neue',
- 'Comic Sans MS'],
- 'font.size': 14.0,
- 'path.sketch': (scale, length, randomness),
- 'path.effects': [patheffects.withStroke(linewidth=4, foreground="w")],
- 'axes.linewidth': 1.5,
- 'lines.linewidth': 2.0,
- 'figure.facecolor': 'white',
- 'grid.linewidth': 0.0,
- 'axes.grid': False,
- 'axes.unicode_minus': False,
- 'axes.edgecolor': 'black',
- 'xtick.major.size': 8,
- 'xtick.major.width': 3,
- 'ytick.major.size': 8,
- 'ytick.major.width': 3,
- })
+ return _xkcd(scale, length, randomness)
+
+
+class _xkcd:
+ # This cannot be implemented in terms of rc_context() because this needs to
+ # work as a non-contextmanager too.
+
+ def __init__(self, scale, length, randomness):
+ self._orig = rcParams.copy()
+
+ if rcParams['text.usetex']:
+ raise RuntimeError(
+ "xkcd mode is not compatible with text.usetex = True")
+
+ from matplotlib import patheffects
+ rcParams.update({
+ 'font.family': ['xkcd', 'xkcd Script', 'Humor Sans', 'Comic Neue',
+ 'Comic Sans MS'],
+ 'font.size': 14.0,
+ 'path.sketch': (scale, length, randomness),
+ 'path.effects': [
+ patheffects.withStroke(linewidth=4, foreground="w")],
+ 'axes.linewidth': 1.5,
+ 'lines.linewidth': 2.0,
+ 'figure.facecolor': 'white',
+ 'grid.linewidth': 0.0,
+ 'axes.grid': False,
+ 'axes.unicode_minus': False,
+ 'axes.edgecolor': 'black',
+ 'xtick.major.size': 8,
+ 'xtick.major.width': 3,
+ 'ytick.major.size': 8,
+ 'ytick.major.width': 3,
+ })
+
+ def __enter__(self):
+ return self
+
+ def __exit__(self, *args):
+ dict.update(rcParams, self._orig)
## Figures ##
diff --git a/lib/matplotlib/tests/test_rcparams.py b/lib/matplotlib/tests/test_rcparams.py
index c23e83c717c3..c3cf1702a2da 100644
--- a/lib/matplotlib/tests/test_rcparams.py
+++ b/lib/matplotlib/tests/test_rcparams.py
@@ -54,6 +54,14 @@ def test_rcparams(tmpdir):
assert mpl.rcParams['lines.linewidth'] == 44
assert mpl.rcParams['lines.linewidth'] == linewidth
+ # test context as decorator (and test reusability, by calling func twice)
+ @mpl.rc_context({'lines.linewidth': 44})
+ def func():
+ assert mpl.rcParams['lines.linewidth'] == 44
+
+ func()
+ func()
+
# test rc_file
mpl.rc_file(rcpath)
assert mpl.rcParams['lines.linewidth'] == 33