Skip to content

Commit 63b6312

Browse files
authored
Merge pull request #15303 from anntzer/rc_ctxd
Make it possible to use rc_context as a decorator.
2 parents db55918 + e4d63b1 commit 63b6312

File tree

5 files changed

+77
-66
lines changed

5 files changed

+77
-66
lines changed
+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
:orphan:
2+
3+
`matplotlib.rc_context` is now a `contextlib.contextmanager`
4+
````````````````````````````````````````````````````````````
5+
6+
`matplotlib.rc_context` can now be used as a decorator (technically, it is now
7+
implemented as a `contextlib.contextmanager`), e.g. ::
8+
9+
@rc_context({"lines.linewidth": 2})
10+
def some_function(...):
11+
...

doc/users/prev_whats_new/whats_new_1.3.rst

+1-1
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ New plotting features
8686
To give your plots a sense of authority that they may be missing,
8787
Michael Droettboom (inspired by the work of many others in
8888
:ghpull:`1329`) has added an `xkcd-style <http://xkcd.com/>`__ sketch
89-
plotting mode. To use it, simply call :func:`matplotlib.pyplot.xkcd`
89+
plotting mode. To use it, simply call `matplotlib.pyplot.xkcd`
9090
before creating your plot. For really fine control, it is also possible
9191
to modify each artist's sketch parameters individually with
9292
:meth:`matplotlib.artist.Artist.set_sketch_params`.

lib/matplotlib/__init__.py

+14-38
Original file line numberDiff line numberDiff line change
@@ -1035,7 +1035,8 @@ def rc_file(fname, *, use_default_template=True):
10351035
if k not in STYLE_BLACKLIST})
10361036

10371037

1038-
class rc_context:
1038+
@contextlib.contextmanager
1039+
def rc_context(rc=None, fname=None):
10391040
"""
10401041
Return a context manager for managing rc settings.
10411042
@@ -1052,49 +1053,24 @@ class rc_context:
10521053
with mpl.rc_context(rc={'text.usetex': True}, fname='screen.rc'):
10531054
plt.plot(x, a)
10541055
1055-
The 'rc' dictionary takes precedence over the settings loaded from
1056-
'fname'. Passing a dictionary only is also valid. For example a
1057-
common usage is::
1056+
The *rc* dictionary takes precedence over the settings loaded from *fname*.
1057+
Passing a dictionary only is also valid. For example, a common usage is::
10581058
1059-
with mpl.rc_context(rc={'interactive': False}):
1059+
with mpl.rc_context({'interactive': False}):
10601060
fig, ax = plt.subplots()
10611061
ax.plot(range(3), range(3))
10621062
fig.savefig('A.png', format='png')
10631063
plt.close(fig)
10641064
"""
1065-
# While it may seem natural to implement rc_context using
1066-
# contextlib.contextmanager, that would entail always calling the finally:
1067-
# clause of the contextmanager (which restores the original rcs) including
1068-
# during garbage collection; as a result, something like `plt.xkcd();
1069-
# gc.collect()` would result in the style being lost (as `xkcd()` is
1070-
# implemented on top of rc_context, and nothing is holding onto context
1071-
# manager except possibly circular references.
1072-
1073-
def __init__(self, rc=None, fname=None):
1074-
self._orig = rcParams.copy()
1075-
try:
1076-
if fname:
1077-
rc_file(fname)
1078-
if rc:
1079-
rcParams.update(rc)
1080-
except Exception:
1081-
self.__fallback()
1082-
raise
1083-
1084-
def __fallback(self):
1085-
# If anything goes wrong, revert to the original rcs.
1086-
updated_backend = self._orig['backend']
1087-
dict.update(rcParams, self._orig)
1088-
# except for the backend. If the context block triggered resolving
1089-
# the auto backend resolution keep that value around
1090-
if self._orig['backend'] is rcsetup._auto_backend_sentinel:
1091-
rcParams['backend'] = updated_backend
1092-
1093-
def __enter__(self):
1094-
return self
1095-
1096-
def __exit__(self, exc_type, exc_value, exc_tb):
1097-
self.__fallback()
1065+
orig = rcParams.copy()
1066+
try:
1067+
if fname:
1068+
rc_file(fname)
1069+
if rc:
1070+
rcParams.update(rc)
1071+
yield
1072+
finally:
1073+
dict.update(rcParams, orig) # Revert to the original rcs.
10981074

10991075

11001076
def use(backend, *, force=True):

lib/matplotlib/pyplot.py

+43-27
Original file line numberDiff line numberDiff line change
@@ -408,12 +408,11 @@ def setp(obj, *args, **kwargs):
408408

409409
def xkcd(scale=1, length=100, randomness=2):
410410
"""
411-
Turn on `xkcd <https://xkcd.com/>`_ sketch-style drawing mode.
412-
This will only have effect on things drawn after this function is
413-
called.
411+
Turn on `xkcd <https://xkcd.com/>`_ sketch-style drawing mode. This will
412+
only have effect on things drawn after this function is called.
414413
415414
For best results, the "Humor Sans" font should be installed: it is
416-
not included with matplotlib.
415+
not included with Matplotlib.
417416
418417
Parameters
419418
----------
@@ -440,29 +439,46 @@ def xkcd(scale=1, length=100, randomness=2):
440439
# This figure will be in regular style
441440
fig2 = plt.figure()
442441
"""
443-
if rcParams['text.usetex']:
444-
raise RuntimeError(
445-
"xkcd mode is not compatible with text.usetex = True")
446-
447-
from matplotlib import patheffects
448-
return rc_context({
449-
'font.family': ['xkcd', 'xkcd Script', 'Humor Sans', 'Comic Neue',
450-
'Comic Sans MS'],
451-
'font.size': 14.0,
452-
'path.sketch': (scale, length, randomness),
453-
'path.effects': [patheffects.withStroke(linewidth=4, foreground="w")],
454-
'axes.linewidth': 1.5,
455-
'lines.linewidth': 2.0,
456-
'figure.facecolor': 'white',
457-
'grid.linewidth': 0.0,
458-
'axes.grid': False,
459-
'axes.unicode_minus': False,
460-
'axes.edgecolor': 'black',
461-
'xtick.major.size': 8,
462-
'xtick.major.width': 3,
463-
'ytick.major.size': 8,
464-
'ytick.major.width': 3,
465-
})
442+
return _xkcd(scale, length, randomness)
443+
444+
445+
class _xkcd:
446+
# This cannot be implemented in terms of rc_context() because this needs to
447+
# work as a non-contextmanager too.
448+
449+
def __init__(self, scale, length, randomness):
450+
self._orig = rcParams.copy()
451+
452+
if rcParams['text.usetex']:
453+
raise RuntimeError(
454+
"xkcd mode is not compatible with text.usetex = True")
455+
456+
from matplotlib import patheffects
457+
rcParams.update({
458+
'font.family': ['xkcd', 'xkcd Script', 'Humor Sans', 'Comic Neue',
459+
'Comic Sans MS'],
460+
'font.size': 14.0,
461+
'path.sketch': (scale, length, randomness),
462+
'path.effects': [
463+
patheffects.withStroke(linewidth=4, foreground="w")],
464+
'axes.linewidth': 1.5,
465+
'lines.linewidth': 2.0,
466+
'figure.facecolor': 'white',
467+
'grid.linewidth': 0.0,
468+
'axes.grid': False,
469+
'axes.unicode_minus': False,
470+
'axes.edgecolor': 'black',
471+
'xtick.major.size': 8,
472+
'xtick.major.width': 3,
473+
'ytick.major.size': 8,
474+
'ytick.major.width': 3,
475+
})
476+
477+
def __enter__(self):
478+
return self
479+
480+
def __exit__(self, *args):
481+
dict.update(rcParams, self._orig)
466482

467483

468484
## Figures ##

lib/matplotlib/tests/test_rcparams.py

+8
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,14 @@ def test_rcparams(tmpdir):
5454
assert mpl.rcParams['lines.linewidth'] == 44
5555
assert mpl.rcParams['lines.linewidth'] == linewidth
5656

57+
# test context as decorator (and test reusability, by calling func twice)
58+
@mpl.rc_context({'lines.linewidth': 44})
59+
def func():
60+
assert mpl.rcParams['lines.linewidth'] == 44
61+
62+
func()
63+
func()
64+
5765
# test rc_file
5866
mpl.rc_file(rcpath)
5967
assert mpl.rcParams['lines.linewidth'] == 33

0 commit comments

Comments
 (0)