diff --git a/lib/matplotlib/artist.py b/lib/matplotlib/artist.py index bbc489502262..c5e54f97a2f2 100644 --- a/lib/matplotlib/artist.py +++ b/lib/matplotlib/artist.py @@ -880,13 +880,8 @@ def _update_property(self, k, v): raise AttributeError('Unknown property %s' % k) return func(v) - store = self.eventson - self.eventson = False - try: - ret = [_update_property(self, k, v) - for k, v in props.items()] - finally: - self.eventson = store + with cbook._setattr_cm(self, eventson=False): + ret = [_update_property(self, k, v) for k, v in props.items()] if len(ret): self.pchanged() diff --git a/lib/matplotlib/backend_bases.py b/lib/matplotlib/backend_bases.py index ddd4e6d3b0ba..4415b6cf8cdc 100644 --- a/lib/matplotlib/backend_bases.py +++ b/lib/matplotlib/backend_bases.py @@ -2113,17 +2113,6 @@ def print_figure(self, filename, dpi=None, facecolor=None, edgecolor=None, tight bbox is calculated. """ - self._is_saving = True - # Remove the figure manager, if any, to avoid resizing the GUI widget. - # Having *no* manager and a *None* manager are currently different (see - # Figure.show); should probably be normalized to None at some point. - _no_manager = object() - if hasattr(self, 'manager'): - manager = self.manager - del self.manager - else: - manager = _no_manager - if format is None: # get format from filename, or from backend's default filetype if isinstance(filename, getattr(os, "PathLike", ())): @@ -2142,104 +2131,107 @@ def print_figure(self, filename, dpi=None, facecolor=None, edgecolor=None, if dpi is None: dpi = rcParams['savefig.dpi'] - if dpi == 'figure': dpi = getattr(self.figure, '_original_dpi', self.figure.dpi) - if facecolor is None: - facecolor = rcParams['savefig.facecolor'] - if edgecolor is None: - edgecolor = rcParams['savefig.edgecolor'] - - origDPI = self.figure.dpi - origfacecolor = self.figure.get_facecolor() - origedgecolor = self.figure.get_edgecolor() - - self.figure.dpi = dpi - self.figure.set_facecolor(facecolor) - self.figure.set_edgecolor(edgecolor) - - bbox_inches = kwargs.pop("bbox_inches", None) - if bbox_inches is None: - bbox_inches = rcParams['savefig.bbox'] - - if bbox_inches: - # call adjust_bbox to save only the given area - if bbox_inches == "tight": - # When bbox_inches == "tight", it saves the figure twice. The - # first save command (to a BytesIO) is just to estimate the - # bounding box of the figure. + # Remove the figure manager, if any, to avoid resizing the GUI widget. + # Some code (e.g. Figure.show) differentiates between having *no* + # manager and a *None* manager, which should be fixed at some point, + # but this should be fine. + with cbook._setattr_cm(self, _is_saving=True, manager=None), \ + cbook._setattr_cm(self.figure, dpi=dpi): + + if facecolor is None: + facecolor = rcParams['savefig.facecolor'] + if edgecolor is None: + edgecolor = rcParams['savefig.edgecolor'] + + origfacecolor = self.figure.get_facecolor() + origedgecolor = self.figure.get_edgecolor() + + self.figure.dpi = dpi + self.figure.set_facecolor(facecolor) + self.figure.set_edgecolor(edgecolor) + + bbox_inches = kwargs.pop("bbox_inches", None) + if bbox_inches is None: + bbox_inches = rcParams['savefig.bbox'] + + if bbox_inches: + # call adjust_bbox to save only the given area + if bbox_inches == "tight": + # When bbox_inches == "tight", it saves the figure twice. + # The first save command (to a BytesIO) is just to estimate + # the bounding box of the figure. + result = print_method( + io.BytesIO(), + dpi=dpi, + facecolor=facecolor, + edgecolor=edgecolor, + orientation=orientation, + dryrun=True, + **kwargs) + renderer = self.figure._cachedRenderer + bbox_inches = self.figure.get_tightbbox(renderer) + + bbox_artists = kwargs.pop("bbox_extra_artists", None) + if bbox_artists is None: + bbox_artists = \ + self.figure.get_default_bbox_extra_artists() + + bbox_filtered = [] + for a in bbox_artists: + bbox = a.get_window_extent(renderer) + if a.get_clip_on(): + clip_box = a.get_clip_box() + if clip_box is not None: + bbox = Bbox.intersection(bbox, clip_box) + clip_path = a.get_clip_path() + if clip_path is not None and bbox is not None: + clip_path = \ + clip_path.get_fully_transformed_path() + bbox = Bbox.intersection( + bbox, clip_path.get_extents()) + if bbox is not None and ( + bbox.width != 0 or bbox.height != 0): + bbox_filtered.append(bbox) + + if bbox_filtered: + _bbox = Bbox.union(bbox_filtered) + trans = Affine2D().scale(1.0 / self.figure.dpi) + bbox_extra = TransformedBbox(_bbox, trans) + bbox_inches = Bbox.union([bbox_inches, bbox_extra]) + + pad = kwargs.pop("pad_inches", None) + if pad is None: + pad = rcParams['savefig.pad_inches'] + + bbox_inches = bbox_inches.padded(pad) + + restore_bbox = tight_bbox.adjust_bbox(self.figure, bbox_inches, + canvas.fixed_dpi) + + _bbox_inches_restore = (bbox_inches, restore_bbox) + else: + _bbox_inches_restore = None + + try: result = print_method( - io.BytesIO(), + filename, dpi=dpi, facecolor=facecolor, edgecolor=edgecolor, orientation=orientation, - dryrun=True, + bbox_inches_restore=_bbox_inches_restore, **kwargs) - renderer = self.figure._cachedRenderer - bbox_inches = self.figure.get_tightbbox(renderer) - - bbox_artists = kwargs.pop("bbox_extra_artists", None) - if bbox_artists is None: - bbox_artists = self.figure.get_default_bbox_extra_artists() - - bbox_filtered = [] - for a in bbox_artists: - bbox = a.get_window_extent(renderer) - if a.get_clip_on(): - clip_box = a.get_clip_box() - if clip_box is not None: - bbox = Bbox.intersection(bbox, clip_box) - clip_path = a.get_clip_path() - if clip_path is not None and bbox is not None: - clip_path = clip_path.get_fully_transformed_path() - bbox = Bbox.intersection(bbox, - clip_path.get_extents()) - if bbox is not None and (bbox.width != 0 or - bbox.height != 0): - bbox_filtered.append(bbox) - - if bbox_filtered: - _bbox = Bbox.union(bbox_filtered) - trans = Affine2D().scale(1.0 / self.figure.dpi) - bbox_extra = TransformedBbox(_bbox, trans) - bbox_inches = Bbox.union([bbox_inches, bbox_extra]) - - pad = kwargs.pop("pad_inches", None) - if pad is None: - pad = rcParams['savefig.pad_inches'] - - bbox_inches = bbox_inches.padded(pad) - - restore_bbox = tight_bbox.adjust_bbox(self.figure, bbox_inches, - canvas.fixed_dpi) - - _bbox_inches_restore = (bbox_inches, restore_bbox) - else: - _bbox_inches_restore = None - - try: - result = print_method( - filename, - dpi=dpi, - facecolor=facecolor, - edgecolor=edgecolor, - orientation=orientation, - bbox_inches_restore=_bbox_inches_restore, - **kwargs) - finally: - if bbox_inches and restore_bbox: - restore_bbox() - - self.figure.dpi = origDPI - self.figure.set_facecolor(origfacecolor) - self.figure.set_edgecolor(origedgecolor) - self.figure.set_canvas(self) - if manager is not _no_manager: - self.manager = manager - self._is_saving = False - return result + finally: + if bbox_inches and restore_bbox: + restore_bbox() + + self.figure.set_facecolor(origfacecolor) + self.figure.set_edgecolor(origedgecolor) + self.figure.set_canvas(self) + return result @classmethod def get_default_filetype(cls): diff --git a/lib/matplotlib/backends/backend_agg.py b/lib/matplotlib/backends/backend_agg.py index c102044cc974..9c854fb09474 100644 --- a/lib/matplotlib/backends/backend_agg.py +++ b/lib/matplotlib/backends/backend_agg.py @@ -486,53 +486,33 @@ def buffer_rgba(self): def print_raw(self, filename_or_obj, *args, **kwargs): FigureCanvasAgg.draw(self) renderer = self.get_renderer() - original_dpi = renderer.dpi - renderer.dpi = self.figure.dpi - if isinstance(filename_or_obj, six.string_types): - fileobj = open(filename_or_obj, 'wb') - close = True - else: - fileobj = filename_or_obj - close = False - try: - fileobj.write(renderer._renderer.buffer_rgba()) - finally: - if close: - fileobj.close() - renderer.dpi = original_dpi + with cbook._setattr_cm(renderer, dpi=self.figure.dpi), \ + cbook.open_file_cm(filename_or_obj, "wb") as fh: + fh.write(renderer._renderer.buffer_rgba()) print_rgba = print_raw def print_png(self, filename_or_obj, *args, **kwargs): FigureCanvasAgg.draw(self) renderer = self.get_renderer() - original_dpi = renderer.dpi - renderer.dpi = self.figure.dpi - version_str = 'matplotlib version ' + __version__ + \ - ', http://matplotlib.org/' + version_str = ( + 'matplotlib version ' + __version__ + ', http://matplotlib.org/') metadata = OrderedDict({'Software': version_str}) user_metadata = kwargs.pop("metadata", None) if user_metadata is not None: metadata.update(user_metadata) - try: - with cbook.open_file_cm(filename_or_obj, "wb") as fh: - _png.write_png(renderer._renderer, fh, - self.figure.dpi, metadata=metadata) - finally: - renderer.dpi = original_dpi + with cbook._setattr_cm(renderer, dpi=self.figure.dpi), \ + cbook.open_file_cm(filename_or_obj, "wb") as fh: + _png.write_png(renderer._renderer, fh, + self.figure.dpi, metadata=metadata) def print_to_buffer(self): FigureCanvasAgg.draw(self) renderer = self.get_renderer() - original_dpi = renderer.dpi - renderer.dpi = self.figure.dpi - try: - result = (renderer._renderer.buffer_rgba(), - (int(renderer.width), int(renderer.height))) - finally: - renderer.dpi = original_dpi - return result + with cbook._setattr_cm(renderer, dpi=self.figure.dpi): + return (renderer._renderer.buffer_rgba(), + (int(renderer.width), int(renderer.height))) if _has_pil: # add JPEG support diff --git a/lib/matplotlib/backends/backend_qt5.py b/lib/matplotlib/backends/backend_qt5.py index a3757d6000bf..9158677c3f29 100644 --- a/lib/matplotlib/backends/backend_qt5.py +++ b/lib/matplotlib/backends/backend_qt5.py @@ -12,6 +12,7 @@ import matplotlib +from matplotlib import backend_tools, cbook from matplotlib._pylab_helpers import Gcf from matplotlib.backend_bases import ( _Backend, FigureCanvasBase, FigureManagerBase, NavigationToolbar2, @@ -20,7 +21,6 @@ from matplotlib.backends.qt_editor.formsubplottool import UiSubplotTool from matplotlib.figure import Figure from matplotlib.backend_managers import ToolManager -from matplotlib import backend_tools from .qt_compat import ( QtCore, QtGui, QtWidgets, _getSaveFileName, is_pyqt5, __version__, QT_API) @@ -169,12 +169,9 @@ def cooperative_qwidget_init(self, *args, **kwargs): @functools.wraps(__init__) def wrapper(self, **kwargs): - try: - QtWidgets.QWidget.__init__ = cooperative_qwidget_init + with cbook._setattr_cm(QtWidgets.QWidget, + __init__=cooperative_qwidget_init): __init__(self, **kwargs) - finally: - # Restore __init__ - QtWidgets.QWidget.__init__ = qwidget_init return wrapper @@ -492,11 +489,8 @@ def draw(self): # that uses the result of the draw() to update plot elements. if self._is_drawing: return - self._is_drawing = True - try: + with cbook._setattr_cm(self, _is_drawing=True): super().draw() - finally: - self._is_drawing = False self.update() def draw_idle(self): diff --git a/lib/matplotlib/cbook/__init__.py b/lib/matplotlib/cbook/__init__.py index 73c48fa728f6..4489d0dbb322 100644 --- a/lib/matplotlib/cbook/__init__.py +++ b/lib/matplotlib/cbook/__init__.py @@ -2070,3 +2070,21 @@ def method(self, *args, **kwargs): raise NotImplementedError("Parent class already defines aliases") cls._alias_map = alias_d return cls + + +@contextlib.contextmanager +def _setattr_cm(obj, **kwargs): + """Temporarily set some attributes; restore original state at context exit. + """ + sentinel = object() + origs = [(attr, getattr(obj, attr, sentinel)) for attr in kwargs] + try: + for attr, val in kwargs.items(): + setattr(obj, attr, val) + yield + finally: + for attr, orig in origs: + if orig is sentinel: + delattr(obj, attr) + else: + setattr(obj, attr, orig) diff --git a/lib/matplotlib/font_manager.py b/lib/matplotlib/font_manager.py index 02cbd9633fcd..f497de51eb23 100644 --- a/lib/matplotlib/font_manager.py +++ b/lib/matplotlib/font_manager.py @@ -218,11 +218,7 @@ def win32InstalledFonts(directory=None, fontext='ttf'): direc = os.path.abspath(direc).lower() if os.path.splitext(direc)[1][1:] in fontext: items.add(direc) - except EnvironmentError: - continue - except WindowsError: - continue - except MemoryError: + except (EnvironmentError, MemoryError, WindowsError): continue return list(items) finally: @@ -520,17 +516,14 @@ def createFontList(fontfiles, fontext='ttf'): seen.add(fname) if fontext == 'afm': try: - fh = open(fpath, 'rb') + with open(fpath, 'rb') as fh: + font = afm.AFM(fh) except EnvironmentError: _log.info("Could not open font file %s", fpath) continue - try: - font = afm.AFM(fh) except RuntimeError: _log.info("Could not parse font file %s", fpath) continue - finally: - fh.close() try: prop = afmFontProperty(fpath, font) except KeyError: diff --git a/lib/matplotlib/style/core.py b/lib/matplotlib/style/core.py index d8a6c2efa4f1..2a836721882b 100644 --- a/lib/matplotlib/style/core.py +++ b/lib/matplotlib/style/core.py @@ -141,11 +141,6 @@ def context(style, after_reset=False): mpl.rcdefaults() try: use(style) - except: - # Restore original settings before raising errors during the update. - mpl.rcParams.update(initial_settings) - raise - else: yield finally: mpl.rcParams.update(initial_settings) diff --git a/lib/matplotlib/testing/conftest.py b/lib/matplotlib/testing/conftest.py index 9dc180c96000..5f7cac230297 100644 --- a/lib/matplotlib/testing/conftest.py +++ b/lib/matplotlib/testing/conftest.py @@ -4,6 +4,7 @@ import pytest import matplotlib +from matplotlib import cbook def pytest_configure(config): @@ -50,8 +51,7 @@ def mpl_test_settings(request): finally: if backend is not None: plt.switch_backend(prev_backend) - _do_cleanup(original_units_registry, - original_settings) + _do_cleanup(original_units_registry, original_settings) @pytest.fixture @@ -71,11 +71,9 @@ def mpl_image_comparison_parameters(request, extension): baseline_images = request.getfixturevalue('baseline_images') func = request.function - func.__wrapped__.parameters = (baseline_images, extension) - try: + with cbook._setattr_cm(func.__wrapped__, + parameters=(baseline_images, extension)): yield - finally: - delattr(func.__wrapped__, 'parameters') @pytest.fixture diff --git a/lib/matplotlib/testing/decorators.py b/lib/matplotlib/testing/decorators.py index 4d985f8c4895..d579a4713535 100644 --- a/lib/matplotlib/testing/decorators.py +++ b/lib/matplotlib/testing/decorators.py @@ -116,8 +116,7 @@ def wrapped_callable(*args, **kwargs): try: yield from func(*args, **kwargs) finally: - _do_cleanup(original_units_registry, - original_settings) + _do_cleanup(original_units_registry, original_settings) else: @functools.wraps(func) def wrapped_callable(*args, **kwargs): @@ -127,8 +126,7 @@ def wrapped_callable(*args, **kwargs): try: func(*args, **kwargs) finally: - _do_cleanup(original_units_registry, - original_settings) + _do_cleanup(original_units_registry, original_settings) return wrapped_callable diff --git a/lib/matplotlib/tests/test_axes.py b/lib/matplotlib/tests/test_axes.py index e8cb3a243fde..479e55b37251 100644 --- a/lib/matplotlib/tests/test_axes.py +++ b/lib/matplotlib/tests/test_axes.py @@ -875,11 +875,8 @@ def test_inverted_limits(): def test_nonfinite_limits(): x = np.arange(0., np.e, 0.01) # silence divide by zero warning from log(0) - olderr = np.seterr(divide='ignore') - try: + with np.errstate(divide='ignore'): y = np.log(x) - finally: - np.seterr(**olderr) x[len(x)//2] = np.nan fig = plt.figure() ax = fig.add_subplot(111) diff --git a/lib/matplotlib/tests/test_backend_ps.py b/lib/matplotlib/tests/test_backend_ps.py index 8768b2669ceb..fd0d192c3e38 100644 --- a/lib/matplotlib/tests/test_backend_ps.py +++ b/lib/matplotlib/tests/test_backend_ps.py @@ -1,12 +1,15 @@ import io +import os +from pathlib import Path import re +import tempfile import numpy as np import pytest import matplotlib import matplotlib.pyplot as plt -from matplotlib import patheffects +from matplotlib import cbook, patheffects from matplotlib.testing.decorators import image_comparison from matplotlib.testing.determinism import (_determinism_source_date_epoch, _determinism_check) @@ -79,40 +82,22 @@ def test_patheffects(): @needs_usetex @needs_ghostscript -def test_tilde_in_tempfilename(): +def test_tilde_in_tempfilename(tmpdir): # Tilde ~ in the tempdir path (e.g. TMPDIR, TMP or TEMP on windows # when the username is very long and windows uses a short name) breaks # latex before https://github.com/matplotlib/matplotlib/pull/5928 - import tempfile - import shutil - import os - import os.path - - tempdir = None - old_tempdir = tempfile.tempdir - try: - # change the path for new tempdirs, which is used - # internally by the ps backend to write a file - tempdir = tempfile.mkdtemp() - base_tempdir = os.path.join(tempdir, "short~1") - os.makedirs(base_tempdir) - tempfile.tempdir = base_tempdir - + base_tempdir = Path(str(tmpdir), "short-1") + base_tempdir.mkdir() + # Change the path for new tempdirs, which is used internally by the ps + # backend to write a file. + with cbook._setattr_cm(tempfile, tempdir=str(base_tempdir)): # usetex results in the latex call, which does not like the ~ plt.rc('text', usetex=True) plt.plot([1, 2, 3, 4]) plt.xlabel(r'\textbf{time} (s)') - output_eps = os.path.join(base_tempdir, 'tex_demo.eps') + output_eps = os.path.join(str(base_tempdir), 'tex_demo.eps') # use the PS backend to write the file... plt.savefig(output_eps, format="ps") - finally: - tempfile.tempdir = old_tempdir - if tempdir: - try: - shutil.rmtree(tempdir) - except Exception as e: - # do not break if this is not removable... - print(e) def test_source_date_epoch():
Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.
Alternative Proxies: