Skip to content

Commit 1c5eaf6

Browse files
authored
Merge pull request #10314 from anntzer/setattr-cm
setattr context manager.
2 parents b1e5b9a + 690b213 commit 1c5eaf6

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
@@ -2071,3 +2071,21 @@ def method(self, *args, **kwargs):
20712071
raise NotImplementedError("Parent class already defines aliases")
20722072
cls._alias_map = alias_d
20732073
return cls
2074+
2075+
2076+
@contextlib.contextmanager
2077+
def _setattr_cm(obj, **kwargs):
2078+
"""Temporarily set some attributes; restore original state at context exit.
2079+
"""
2080+
sentinel = object()
2081+
origs = [(attr, getattr(obj, attr, sentinel)) for attr in kwargs]
2082+
try:
2083+
for attr, val in kwargs.items():
2084+
setattr(obj, attr, val)
2085+
yield
2086+
finally:
2087+
for attr, orig in origs:
2088+
if orig is sentinel:
2089+
delattr(obj, attr)
2090+
else:
2091+
setattr(obj, attr, orig)

lib/matplotlib/font_manager.py

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -220,11 +220,7 @@ def win32InstalledFonts(directory=None, fontext='ttf'):
220220
direc = os.path.abspath(direc).lower()
221221
if os.path.splitext(direc)[1][1:] in fontext:
222222
items.add(direc)
223-
except EnvironmentError:
224-
continue
225-
except WindowsError:
226-
continue
227-
except MemoryError:
223+
except (EnvironmentError, MemoryError, WindowsError):
228224
continue
229225
return list(items)
230226
finally:
@@ -514,17 +510,14 @@ def createFontList(fontfiles, fontext='ttf'):
514510
seen.add(fname)
515511
if fontext == 'afm':
516512
try:
517-
fh = open(fpath, 'rb')
513+
with open(fpath, 'rb') as fh:
514+
font = afm.AFM(fh)
518515
except EnvironmentError:
519516
_log.info("Could not open font file %s", fpath)
520517
continue
521-
try:
522-
font = afm.AFM(fh)
523518
except RuntimeError:
524519
_log.info("Could not parse font file %s", fpath)
525520
continue
526-
finally:
527-
fh.close()
528521
try:
529522
prop = afmFontProperty(fpath, font)
530523
except KeyError:

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

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:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy