Skip to content

Commit 690b213

Browse files
committed
setattr context manager.
1 parent 5e52ce1 commit 690b213

File tree

11 files changed

+149
-204
lines changed

11 files changed

+149
-204
lines changed

lib/matplotlib/artist.py

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -880,13 +880,8 @@ def _update_property(self, k, v):
880880
raise AttributeError('Unknown property %s' % k)
881881
return func(v)
882882

883-
store = self.eventson
884-
self.eventson = False
885-
try:
886-
ret = [_update_property(self, k, v)
887-
for k, v in props.items()]
888-
finally:
889-
self.eventson = store
883+
with cbook._setattr_cm(self, eventson=False):
884+
ret = [_update_property(self, k, v) for k, v in props.items()]
890885

891886
if len(ret):
892887
self.pchanged()

lib/matplotlib/backend_bases.py

Lines changed: 92 additions & 100 deletions
Original file line numberDiff line numberDiff line change
@@ -2113,17 +2113,6 @@ def print_figure(self, filename, dpi=None, facecolor=None, edgecolor=None,
21132113
tight bbox is calculated.
21142114
21152115
"""
2116-
self._is_saving = True
2117-
# Remove the figure manager, if any, to avoid resizing the GUI widget.
2118-
# Having *no* manager and a *None* manager are currently different (see
2119-
# Figure.show); should probably be normalized to None at some point.
2120-
_no_manager = object()
2121-
if hasattr(self, 'manager'):
2122-
manager = self.manager
2123-
del self.manager
2124-
else:
2125-
manager = _no_manager
2126-
21272116
if format is None:
21282117
# get format from filename, or from backend's default filetype
21292118
if isinstance(filename, getattr(os, "PathLike", ())):
@@ -2142,104 +2131,107 @@ def print_figure(self, filename, dpi=None, facecolor=None, edgecolor=None,
21422131

21432132
if dpi is None:
21442133
dpi = rcParams['savefig.dpi']
2145-
21462134
if dpi == 'figure':
21472135
dpi = getattr(self.figure, '_original_dpi', self.figure.dpi)
21482136

2149-
if facecolor is None:
2150-
facecolor = rcParams['savefig.facecolor']
2151-
if edgecolor is None:
2152-
edgecolor = rcParams['savefig.edgecolor']
2153-
2154-
origDPI = self.figure.dpi
2155-
origfacecolor = self.figure.get_facecolor()
2156-
origedgecolor = self.figure.get_edgecolor()
2157-
2158-
self.figure.dpi = dpi
2159-
self.figure.set_facecolor(facecolor)
2160-
self.figure.set_edgecolor(edgecolor)
2161-
2162-
bbox_inches = kwargs.pop("bbox_inches", None)
2163-
if bbox_inches is None:
2164-
bbox_inches = rcParams['savefig.bbox']
2165-
2166-
if bbox_inches:
2167-
# call adjust_bbox to save only the given area
2168-
if bbox_inches == "tight":
2169-
# When bbox_inches == "tight", it saves the figure twice. The
2170-
# first save command (to a BytesIO) is just to estimate the
2171-
# bounding box of the figure.
2137+
# Remove the figure manager, if any, to avoid resizing the GUI widget.
2138+
# Some code (e.g. Figure.show) differentiates between having *no*
2139+
# manager and a *None* manager, which should be fixed at some point,
2140+
# but this should be fine.
2141+
with cbook._setattr_cm(self, _is_saving=True, manager=None), \
2142+
cbook._setattr_cm(self.figure, dpi=dpi):
2143+
2144+
if facecolor is None:
2145+
facecolor = rcParams['savefig.facecolor']
2146+
if edgecolor is None:
2147+
edgecolor = rcParams['savefig.edgecolor']
2148+
2149+
origfacecolor = self.figure.get_facecolor()
2150+
origedgecolor = self.figure.get_edgecolor()
2151+
2152+
self.figure.dpi = dpi
2153+
self.figure.set_facecolor(facecolor)
2154+
self.figure.set_edgecolor(edgecolor)
2155+
2156+
bbox_inches = kwargs.pop("bbox_inches", None)
2157+
if bbox_inches is None:
2158+
bbox_inches = rcParams['savefig.bbox']
2159+
2160+
if bbox_inches:
2161+
# call adjust_bbox to save only the given area
2162+
if bbox_inches == "tight":
2163+
# When bbox_inches == "tight", it saves the figure twice.
2164+
# The first save command (to a BytesIO) is just to estimate
2165+
# the bounding box of the figure.
2166+
result = print_method(
2167+
io.BytesIO(),
2168+
dpi=dpi,
2169+
facecolor=facecolor,
2170+
edgecolor=edgecolor,
2171+
orientation=orientation,
2172+
dryrun=True,
2173+
**kwargs)
2174+
renderer = self.figure._cachedRenderer
2175+
bbox_inches = self.figure.get_tightbbox(renderer)
2176+
2177+
bbox_artists = kwargs.pop("bbox_extra_artists", None)
2178+
if bbox_artists is None:
2179+
bbox_artists = \
2180+
self.figure.get_default_bbox_extra_artists()
2181+
2182+
bbox_filtered = []
2183+
for a in bbox_artists:
2184+
bbox = a.get_window_extent(renderer)
2185+
if a.get_clip_on():
2186+
clip_box = a.get_clip_box()
2187+
if clip_box is not None:
2188+
bbox = Bbox.intersection(bbox, clip_box)
2189+
clip_path = a.get_clip_path()
2190+
if clip_path is not None and bbox is not None:
2191+
clip_path = \
2192+
clip_path.get_fully_transformed_path()
2193+
bbox = Bbox.intersection(
2194+
bbox, clip_path.get_extents())
2195+
if bbox is not None and (
2196+
bbox.width != 0 or bbox.height != 0):
2197+
bbox_filtered.append(bbox)
2198+
2199+
if bbox_filtered:
2200+
_bbox = Bbox.union(bbox_filtered)
2201+
trans = Affine2D().scale(1.0 / self.figure.dpi)
2202+
bbox_extra = TransformedBbox(_bbox, trans)
2203+
bbox_inches = Bbox.union([bbox_inches, bbox_extra])
2204+
2205+
pad = kwargs.pop("pad_inches", None)
2206+
if pad is None:
2207+
pad = rcParams['savefig.pad_inches']
2208+
2209+
bbox_inches = bbox_inches.padded(pad)
2210+
2211+
restore_bbox = tight_bbox.adjust_bbox(self.figure, bbox_inches,
2212+
canvas.fixed_dpi)
2213+
2214+
_bbox_inches_restore = (bbox_inches, restore_bbox)
2215+
else:
2216+
_bbox_inches_restore = None
2217+
2218+
try:
21722219
result = print_method(
2173-
io.BytesIO(),
2220+
filename,
21742221
dpi=dpi,
21752222
facecolor=facecolor,
21762223
edgecolor=edgecolor,
21772224
orientation=orientation,
2178-
dryrun=True,
2225+
bbox_inches_restore=_bbox_inches_restore,
21792226
**kwargs)
2180-
renderer = self.figure._cachedRenderer
2181-
bbox_inches = self.figure.get_tightbbox(renderer)
2182-
2183-
bbox_artists = kwargs.pop("bbox_extra_artists", None)
2184-
if bbox_artists is None:
2185-
bbox_artists = self.figure.get_default_bbox_extra_artists()
2186-
2187-
bbox_filtered = []
2188-
for a in bbox_artists:
2189-
bbox = a.get_window_extent(renderer)
2190-
if a.get_clip_on():
2191-
clip_box = a.get_clip_box()
2192-
if clip_box is not None:
2193-
bbox = Bbox.intersection(bbox, clip_box)
2194-
clip_path = a.get_clip_path()
2195-
if clip_path is not None and bbox is not None:
2196-
clip_path = clip_path.get_fully_transformed_path()
2197-
bbox = Bbox.intersection(bbox,
2198-
clip_path.get_extents())
2199-
if bbox is not None and (bbox.width != 0 or
2200-
bbox.height != 0):
2201-
bbox_filtered.append(bbox)
2202-
2203-
if bbox_filtered:
2204-
_bbox = Bbox.union(bbox_filtered)
2205-
trans = Affine2D().scale(1.0 / self.figure.dpi)
2206-
bbox_extra = TransformedBbox(_bbox, trans)
2207-
bbox_inches = Bbox.union([bbox_inches, bbox_extra])
2208-
2209-
pad = kwargs.pop("pad_inches", None)
2210-
if pad is None:
2211-
pad = rcParams['savefig.pad_inches']
2212-
2213-
bbox_inches = bbox_inches.padded(pad)
2214-
2215-
restore_bbox = tight_bbox.adjust_bbox(self.figure, bbox_inches,
2216-
canvas.fixed_dpi)
2217-
2218-
_bbox_inches_restore = (bbox_inches, restore_bbox)
2219-
else:
2220-
_bbox_inches_restore = None
2221-
2222-
try:
2223-
result = print_method(
2224-
filename,
2225-
dpi=dpi,
2226-
facecolor=facecolor,
2227-
edgecolor=edgecolor,
2228-
orientation=orientation,
2229-
bbox_inches_restore=_bbox_inches_restore,
2230-
**kwargs)
2231-
finally:
2232-
if bbox_inches and restore_bbox:
2233-
restore_bbox()
2234-
2235-
self.figure.dpi = origDPI
2236-
self.figure.set_facecolor(origfacecolor)
2237-
self.figure.set_edgecolor(origedgecolor)
2238-
self.figure.set_canvas(self)
2239-
if manager is not _no_manager:
2240-
self.manager = manager
2241-
self._is_saving = False
2242-
return result
2227+
finally:
2228+
if bbox_inches and restore_bbox:
2229+
restore_bbox()
2230+
2231+
self.figure.set_facecolor(origfacecolor)
2232+
self.figure.set_edgecolor(origedgecolor)
2233+
self.figure.set_canvas(self)
2234+
return result
22432235

22442236
@classmethod
22452237
def get_default_filetype(cls):

lib/matplotlib/backends/backend_agg.py

Lines changed: 12 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -486,53 +486,33 @@ def buffer_rgba(self):
486486
def print_raw(self, filename_or_obj, *args, **kwargs):
487487
FigureCanvasAgg.draw(self)
488488
renderer = self.get_renderer()
489-
original_dpi = renderer.dpi
490-
renderer.dpi = self.figure.dpi
491-
if isinstance(filename_or_obj, six.string_types):
492-
fileobj = open(filename_or_obj, 'wb')
493-
close = True
494-
else:
495-
fileobj = filename_or_obj
496-
close = False
497-
try:
498-
fileobj.write(renderer._renderer.buffer_rgba())
499-
finally:
500-
if close:
501-
fileobj.close()
502-
renderer.dpi = original_dpi
489+
with cbook._setattr_cm(renderer, dpi=self.figure.dpi), \
490+
cbook.open_file_cm(filename_or_obj, "wb") as fh:
491+
fh.write(renderer._renderer.buffer_rgba())
503492
print_rgba = print_raw
504493

505494
def print_png(self, filename_or_obj, *args, **kwargs):
506495
FigureCanvasAgg.draw(self)
507496
renderer = self.get_renderer()
508-
original_dpi = renderer.dpi
509-
renderer.dpi = self.figure.dpi
510497

511-
version_str = 'matplotlib version ' + __version__ + \
512-
', http://matplotlib.org/'
498+
version_str = (
499+
'matplotlib version ' + __version__ + ', http://matplotlib.org/')
513500
metadata = OrderedDict({'Software': version_str})
514501
user_metadata = kwargs.pop("metadata", None)
515502
if user_metadata is not None:
516503
metadata.update(user_metadata)
517504

518-
try:
519-
with cbook.open_file_cm(filename_or_obj, "wb") as fh:
520-
_png.write_png(renderer._renderer, fh,
521-
self.figure.dpi, metadata=metadata)
522-
finally:
523-
renderer.dpi = original_dpi
505+
with cbook._setattr_cm(renderer, dpi=self.figure.dpi), \
506+
cbook.open_file_cm(filename_or_obj, "wb") as fh:
507+
_png.write_png(renderer._renderer, fh,
508+
self.figure.dpi, metadata=metadata)
524509

525510
def print_to_buffer(self):
526511
FigureCanvasAgg.draw(self)
527512
renderer = self.get_renderer()
528-
original_dpi = renderer.dpi
529-
renderer.dpi = self.figure.dpi
530-
try:
531-
result = (renderer._renderer.buffer_rgba(),
532-
(int(renderer.width), int(renderer.height)))
533-
finally:
534-
renderer.dpi = original_dpi
535-
return result
513+
with cbook._setattr_cm(renderer, dpi=self.figure.dpi):
514+
return (renderer._renderer.buffer_rgba(),
515+
(int(renderer.width), int(renderer.height)))
536516

537517
if _has_pil:
538518
# add JPEG support

lib/matplotlib/backends/backend_qt5.py

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212

1313
import matplotlib
1414

15+
from matplotlib import backend_tools, cbook
1516
from matplotlib._pylab_helpers import Gcf
1617
from matplotlib.backend_bases import (
1718
_Backend, FigureCanvasBase, FigureManagerBase, NavigationToolbar2,
@@ -20,7 +21,6 @@
2021
from matplotlib.backends.qt_editor.formsubplottool import UiSubplotTool
2122
from matplotlib.figure import Figure
2223
from matplotlib.backend_managers import ToolManager
23-
from matplotlib import backend_tools
2424

2525
from .qt_compat import (
2626
QtCore, QtGui, QtWidgets, _getSaveFileName, is_pyqt5, __version__, QT_API)
@@ -169,12 +169,9 @@ def cooperative_qwidget_init(self, *args, **kwargs):
169169

170170
@functools.wraps(__init__)
171171
def wrapper(self, **kwargs):
172-
try:
173-
QtWidgets.QWidget.__init__ = cooperative_qwidget_init
172+
with cbook._setattr_cm(QtWidgets.QWidget,
173+
__init__=cooperative_qwidget_init):
174174
__init__(self, **kwargs)
175-
finally:
176-
# Restore __init__
177-
QtWidgets.QWidget.__init__ = qwidget_init
178175

179176
return wrapper
180177

@@ -492,11 +489,8 @@ def draw(self):
492489
# that uses the result of the draw() to update plot elements.
493490
if self._is_drawing:
494491
return
495-
self._is_drawing = True
496-
try:
492+
with cbook._setattr_cm(self, _is_drawing=True):
497493
super().draw()
498-
finally:
499-
self._is_drawing = False
500494
self.update()
501495

502496
def draw_idle(self):

lib/matplotlib/cbook/__init__.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2070,3 +2070,21 @@ def method(self, *args, **kwargs):
20702070
raise NotImplementedError("Parent class already defines aliases")
20712071
cls._alias_map = alias_d
20722072
return cls
2073+
2074+
2075+
@contextlib.contextmanager
2076+
def _setattr_cm(obj, **kwargs):
2077+
"""Temporarily set some attributes; restore original state at context exit.
2078+
"""
2079+
sentinel = object()
2080+
origs = [(attr, getattr(obj, attr, sentinel)) for attr in kwargs]
2081+
try:
2082+
for attr, val in kwargs.items():
2083+
setattr(obj, attr, val)
2084+
yield
2085+
finally:
2086+
for attr, orig in origs:
2087+
if orig is sentinel:
2088+
delattr(obj, attr)
2089+
else:
2090+
setattr(obj, attr, orig)

lib/matplotlib/font_manager.py

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -218,11 +218,7 @@ def win32InstalledFonts(directory=None, fontext='ttf'):
218218
direc = os.path.abspath(direc).lower()
219219
if os.path.splitext(direc)[1][1:] in fontext:
220220
items.add(direc)
221-
except EnvironmentError:
222-
continue
223-
except WindowsError:
224-
continue
225-
except MemoryError:
221+
except (EnvironmentError, MemoryError, WindowsError):
226222
continue
227223
return list(items)
228224
finally:
@@ -520,17 +516,14 @@ def createFontList(fontfiles, fontext='ttf'):
520516
seen.add(fname)
521517
if fontext == 'afm':
522518
try:
523-
fh = open(fpath, 'rb')
519+
with open(fpath, 'rb') as fh:
520+
font = afm.AFM(fh)
524521
except EnvironmentError:
525522
_log.info("Could not open font file %s", fpath)
526523
continue
527-
try:
528-
font = afm.AFM(fh)
529524
except RuntimeError:
530525
_log.info("Could not parse font file %s", fpath)
531526
continue
532-
finally:
533-
fh.close()
534527
try:
535528
prop = afmFontProperty(fpath, font)
536529
except KeyError:

0 commit comments

Comments
 (0)