Skip to content

Commit 94c63af

Browse files
authored
Merge pull request #10860 from matplotlib/auto-backport-of-pr-10856
Backport PR #10856 on branch v2.2.x
2 parents 6afe2f9 + 960e73e commit 94c63af

File tree

3 files changed

+31
-30
lines changed

3 files changed

+31
-30
lines changed

lib/matplotlib/__init__.py

+25-12
Original file line numberDiff line numberDiff line change
@@ -1294,8 +1294,7 @@ def rc_file(fname):
12941294
rcParams.update(rc_params_from_file(fname))
12951295

12961296

1297-
@contextlib.contextmanager
1298-
def rc_context(rc=None, fname=None):
1297+
class rc_context(object):
12991298
"""
13001299
Return a context manager for managing rc settings.
13011300
@@ -1325,19 +1324,33 @@ def rc_context(rc=None, fname=None):
13251324
ax.plot(range(3), range(3))
13261325
fig.savefig('A.png', format='png')
13271326
plt.close(fig)
1328-
13291327
"""
1328+
# While it may seem natural to implement rc_context using
1329+
# contextlib.contextmanager, that would entail always calling the finally:
1330+
# clause of the contextmanager (which restores the original rcs) including
1331+
# during garbage collection; as a result, something like `plt.xkcd();
1332+
# gc.collect()` would result in the style being lost (as `xkcd()` is
1333+
# implemented on top of rc_context, and nothing is holding onto context
1334+
# manager except possibly circular references.
1335+
1336+
def __init__(self, rc=None, fname=None):
1337+
self._orig = rcParams.copy()
1338+
try:
1339+
if fname:
1340+
rc_file(fname)
1341+
if rc:
1342+
rcParams.update(rc)
1343+
except Exception:
1344+
# If anything goes wrong, revert to the original rcs.
1345+
dict.update(rcParams, self._orig)
1346+
raise
13301347

1331-
orig = rcParams.copy()
1332-
try:
1333-
if fname:
1334-
rc_file(fname)
1335-
if rc:
1336-
rcParams.update(rc)
1337-
yield
1338-
finally:
1348+
def __enter__(self):
1349+
return self
1350+
1351+
def __exit__(self, exc_type, exc_value, exc_tb):
13391352
# No need to revalidate the original values.
1340-
dict.update(rcParams, orig)
1353+
dict.update(rcParams, self._orig)
13411354

13421355

13431356
_use_error_msg = """

lib/matplotlib/pyplot.py

+1-16
Original file line numberDiff line numberDiff line change
@@ -392,7 +392,7 @@ def xkcd(scale=1, length=100, randomness=2):
392392
"xkcd mode is not compatible with text.usetex = True")
393393

394394
from matplotlib import patheffects
395-
xkcd_ctx = rc_context({
395+
return rc_context({
396396
'font.family': ['xkcd', 'Humor Sans', 'Comic Sans MS'],
397397
'font.size': 14.0,
398398
'path.sketch': (scale, length, randomness),
@@ -409,21 +409,6 @@ def xkcd(scale=1, length=100, randomness=2):
409409
'ytick.major.size': 8,
410410
'ytick.major.width': 3,
411411
})
412-
xkcd_ctx.__enter__()
413-
414-
# In order to make the call to `xkcd` that does not use a context manager
415-
# (cm) work, we need to enter into the cm ourselves, and return a dummy
416-
# cm that does nothing on entry and cleans up the xkcd context on exit.
417-
# Additionally, we need to keep a reference to the dummy cm because it
418-
# would otherwise be exited when GC'd.
419-
420-
class dummy_ctx(object):
421-
def __enter__(self):
422-
pass
423-
424-
__exit__ = xkcd_ctx.__exit__
425-
426-
return dummy_ctx()
427412

428413

429414
## Figures ##

lib/matplotlib/tests/test_style.py

+5-2
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
from __future__ import absolute_import, division, print_function
22

3+
from collections import OrderedDict
4+
from contextlib import contextmanager
5+
import gc
36
import os
47
import shutil
58
import tempfile
69
import warnings
7-
from collections import OrderedDict
8-
from contextlib import contextmanager
910

1011
import pytest
1112

@@ -163,6 +164,8 @@ def test_xkcd_no_cm():
163164
assert mpl.rcParams["path.sketch"] is None
164165
plt.xkcd()
165166
assert mpl.rcParams["path.sketch"] == (1, 100, 2)
167+
gc.collect()
168+
assert mpl.rcParams["path.sketch"] == (1, 100, 2)
166169

167170

168171
def test_xkcd_cm():

0 commit comments

Comments
 (0)