Skip to content

Commit 4c1e36d

Browse files
committed
Merge pull request matplotlib#1175 from pelson/pickle2
Pickling support added. Various whitespace fixes as a result of reading *lots* of code.
2 parents cf7618c + 1e08190 commit 4c1e36d

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

47 files changed

+1073
-517
lines changed

doc/users/whats_new.rst

+11
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,17 @@ minimum and maximum colorbar extensions.
100100
plt.show()
101101

102102

103+
Figures are picklable
104+
---------------------
105+
106+
Philip Elson added an experimental feature to make figures picklable
107+
for quick and easy short-term storage of plots. Pickle files
108+
are not designed for long term storage, are unsupported when restoring a pickle
109+
saved in another matplotlib version and are insecure when restoring a pickle
110+
from an untrusted source. Having said this, they are useful for short term
111+
storage for later modification inside matplotlib.
112+
113+
103114
Set default bounding box in matplotlibrc
104115
------------------------------------------
105116

lib/matplotlib/__init__.py

+1
Original file line numberDiff line numberDiff line change
@@ -1085,6 +1085,7 @@ def tk_window_focus():
10851085
'matplotlib.tests.test_mathtext',
10861086
'matplotlib.tests.test_mlab',
10871087
'matplotlib.tests.test_patches',
1088+
'matplotlib.tests.test_pickle',
10881089
'matplotlib.tests.test_rcparams',
10891090
'matplotlib.tests.test_simplification',
10901091
'matplotlib.tests.test_spines',

lib/matplotlib/_pylab_helpers.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ def error_msg(msg):
1414

1515
class Gcf(object):
1616
"""
17-
Manage a set of integer-numbered figures.
17+
Singleton to manage a set of integer-numbered figures.
1818
1919
This class is never instantiated; it consists of two class
2020
attributes (a list and a dictionary), and a set of static
@@ -132,6 +132,7 @@ def set_active(manager):
132132
Gcf._activeQue.append(manager)
133133
Gcf.figs[manager.num] = manager
134134

135+
135136
atexit.register(Gcf.destroy_all)
136137

137138

lib/matplotlib/artist.py

+8-1
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,13 @@ def __init__(self):
103103
self._gid = None
104104
self._snap = None
105105

106+
def __getstate__(self):
107+
d = self.__dict__.copy()
108+
# remove the unpicklable remove method, this will get re-added on load
109+
# (by the axes) if the artist lives on an axes.
110+
d['_remove_method'] = None
111+
return d
112+
106113
def remove(self):
107114
"""
108115
Remove the artist from the figure if possible. The effect
@@ -122,7 +129,7 @@ def remove(self):
122129
# the _remove_method attribute directly. This would be a protected
123130
# attribute if Python supported that sort of thing. The callback
124131
# has one parameter, which is the child to be removed.
125-
if self._remove_method != None:
132+
if self._remove_method is not None:
126133
self._remove_method(self)
127134
else:
128135
raise NotImplementedError('cannot remove artist')

lib/matplotlib/axes.py

+60-19
Original file line numberDiff line numberDiff line change
@@ -153,9 +153,8 @@ def set_default_color_cycle(clist):
153153
DeprecationWarning)
154154

155155

156-
class _process_plot_var_args:
156+
class _process_plot_var_args(object):
157157
"""
158-
159158
Process variable length arguments to the plot command, so that
160159
plot commands like the following are supported::
161160
@@ -171,6 +170,14 @@ def __init__(self, axes, command='plot'):
171170
self.command = command
172171
self.set_color_cycle()
173172

173+
def __getstate__(self):
174+
# note: it is not possible to pickle a itertools.cycle instance
175+
return {'axes': self.axes, 'command': self.command}
176+
177+
def __setstate__(self, state):
178+
self.__dict__ = state.copy()
179+
self.set_color_cycle()
180+
174181
def set_color_cycle(self, clist=None):
175182
if clist is None:
176183
clist = rcParams['axes.color_cycle']
@@ -281,7 +288,7 @@ def _plot_args(self, tup, kwargs):
281288
linestyle, marker, color = _process_plot_format(tup[-1])
282289
tup = tup[:-1]
283290
elif len(tup) == 3:
284-
raise ValueError, 'third arg must be a format string'
291+
raise ValueError('third arg must be a format string')
285292
else:
286293
linestyle, marker, color = None, None, None
287294
kw = {}
@@ -354,6 +361,7 @@ class Axes(martist.Artist):
354361

355362
def __str__(self):
356363
return "Axes(%g,%g;%gx%g)" % tuple(self._position.bounds)
364+
357365
def __init__(self, fig, rect,
358366
axisbg = None, # defaults to rc axes.facecolor
359367
frameon = True,
@@ -488,6 +496,15 @@ def __init__(self, fig, rect,
488496
self._ycid = self.yaxis.callbacks.connect('units finalize',
489497
self.relim)
490498

499+
def __setstate__(self, state):
500+
self.__dict__ = state
501+
# put the _remove_method back on all artists contained within the axes
502+
for container_name in ['lines', 'collections', 'tables', 'patches',
503+
'texts', 'images']:
504+
container = getattr(self, container_name)
505+
for artist in container:
506+
artist._remove_method = container.remove
507+
491508
def get_window_extent(self, *args, **kwargs):
492509
"""
493510
get the axes bounding box in display space; *args* and
@@ -1472,7 +1489,7 @@ def _update_line_limits(self, line):
14721489
return
14731490

14741491
line_trans = line.get_transform()
1475-
1492+
14761493
if line_trans == self.transData:
14771494
data_path = path
14781495

@@ -1491,8 +1508,8 @@ def _update_line_limits(self, line):
14911508
else:
14921509
data_path = trans_to_data.transform_path(path)
14931510
else:
1494-
# for backwards compatibility we update the dataLim with the
1495-
# coordinate range of the given path, even though the coordinate
1511+
# for backwards compatibility we update the dataLim with the
1512+
# coordinate range of the given path, even though the coordinate
14961513
# systems are completely different. This may occur in situations
14971514
# such as when ax.transAxes is passed through for absolute
14981515
# positioning.
@@ -1502,7 +1519,7 @@ def _update_line_limits(self, line):
15021519
updatex, updatey = line_trans.contains_branch_seperately(
15031520
self.transData
15041521
)
1505-
self.dataLim.update_from_path(data_path,
1522+
self.dataLim.update_from_path(data_path,
15061523
self.ignore_existing_data_limits,
15071524
updatex=updatex,
15081525
updatey=updatey)
@@ -2199,11 +2216,11 @@ def ticklabel_format(self, **kwargs):
21992216
cb = False
22002217
else:
22012218
cb = True
2202-
raise NotImplementedError, "comma style remains to be added"
2219+
raise NotImplementedError("comma style remains to be added")
22032220
elif style == '':
22042221
sb = None
22052222
else:
2206-
raise ValueError, "%s is not a valid style value"
2223+
raise ValueError("%s is not a valid style value")
22072224
try:
22082225
if sb is not None:
22092226
if axis == 'both' or axis == 'x':
@@ -3706,9 +3723,9 @@ def hlines(self, y, xmin, xmax, colors='k', linestyles='solid',
37063723
xmax = np.resize( xmax, y.shape )
37073724

37083725
if len(xmin)!=len(y):
3709-
raise ValueError, 'xmin and y are unequal sized sequences'
3726+
raise ValueError('xmin and y are unequal sized sequences')
37103727
if len(xmax)!=len(y):
3711-
raise ValueError, 'xmax and y are unequal sized sequences'
3728+
raise ValueError('xmax and y are unequal sized sequences')
37123729

37133730
verts = [ ((thisxmin, thisy), (thisxmax, thisy))
37143731
for thisxmin, thisxmax, thisy in zip(xmin, xmax, y)]
@@ -3785,9 +3802,9 @@ def vlines(self, x, ymin, ymax, colors='k', linestyles='solid',
37853802
ymax = np.resize( ymax, x.shape )
37863803

37873804
if len(ymin)!=len(x):
3788-
raise ValueError, 'ymin and x are unequal sized sequences'
3805+
raise ValueError('ymin and x are unequal sized sequences')
37893806
if len(ymax)!=len(x):
3790-
raise ValueError, 'ymax and x are unequal sized sequences'
3807+
raise ValueError('ymax and x are unequal sized sequences')
37913808

37923809
Y = np.array([ymin, ymax]).T
37933810

@@ -4768,7 +4785,7 @@ def make_iterable(x):
47684785
if len(height) == 1:
47694786
height *= nbars
47704787
else:
4771-
raise ValueError, 'invalid orientation: %s' % orientation
4788+
raise ValueError('invalid orientation: %s' % orientation)
47724789

47734790
if len(linewidth) < nbars:
47744791
linewidth *= nbars
@@ -4826,7 +4843,7 @@ def make_iterable(x):
48264843
bottom = [bottom[i] - height[i]/2. for i in xrange(len(bottom))]
48274844

48284845
else:
4829-
raise ValueError, 'invalid alignment: %s' % align
4846+
raise ValueError('invalid alignment: %s' % align)
48304847

48314848
args = zip(left, bottom, width, height, color, edgecolor, linewidth)
48324849
for l, b, w, h, c, e, lw in args:
@@ -5701,7 +5718,7 @@ def computeConfInterval(data, med, iq, bootstrap):
57015718
else:
57025719
x = [x[:,i] for i in xrange(nc)]
57035720
else:
5704-
raise ValueError, "input x can have no more than 2 dimensions"
5721+
raise ValueError("input x can have no more than 2 dimensions")
57055722
if not hasattr(x[0], '__len__'):
57065723
x = [x]
57075724
col = len(x)
@@ -7069,10 +7086,10 @@ def _pcolorargs(self, funcname, *args):
70697086

70707087
Nx = X.shape[-1]
70717088
Ny = Y.shape[0]
7072-
if len(X.shape) <> 2 or X.shape[0] == 1:
7089+
if len(X.shape) != 2 or X.shape[0] == 1:
70737090
x = X.reshape(1,Nx)
70747091
X = x.repeat(Ny, axis=0)
7075-
if len(Y.shape) <> 2 or Y.shape[1] == 1:
7092+
if len(Y.shape) != 2 or Y.shape[1] == 1:
70767093
y = Y.reshape(Ny, 1)
70777094
Y = y.repeat(Nx, axis=1)
70787095
if X.shape != Y.shape:
@@ -8815,7 +8832,15 @@ def __init__(self, fig, *args, **kwargs):
88158832
# _axes_class is set in the subplot_class_factory
88168833
self._axes_class.__init__(self, fig, self.figbox, **kwargs)
88178834

8818-
8835+
def __reduce__(self):
8836+
# get the first axes class which does not inherit from a subplotbase
8837+
not_subplotbase = lambda c: issubclass(c, Axes) and \
8838+
not issubclass(c, SubplotBase)
8839+
axes_class = [c for c in self.__class__.mro() if not_subplotbase(c)][0]
8840+
r = [_PicklableSubplotClassConstructor(),
8841+
(axes_class,),
8842+
self.__getstate__()]
8843+
return tuple(r)
88198844

88208845
def get_geometry(self):
88218846
"""get the subplot geometry, eg 2,2,3"""
@@ -8897,6 +8922,22 @@ def subplot_class_factory(axes_class=None):
88978922
# This is provided for backward compatibility
88988923
Subplot = subplot_class_factory()
88998924

8925+
8926+
class _PicklableSubplotClassConstructor(object):
8927+
"""
8928+
This stub class exists to return the appropriate subplot
8929+
class when __call__-ed with an axes class. This is purely to
8930+
allow Pickling of Axes and Subplots.
8931+
"""
8932+
def __call__(self, axes_class):
8933+
# create a dummy object instance
8934+
subplot_instance = _PicklableSubplotClassConstructor()
8935+
subplot_class = subplot_class_factory(axes_class)
8936+
# update the class to the desired subplot class
8937+
subplot_instance.__class__ = subplot_class
8938+
return subplot_instance
8939+
8940+
89008941
docstring.interpd.update(Axes=martist.kwdoc(Axes))
89018942
docstring.interpd.update(Subplot=martist.kwdoc(Axes))
89028943

lib/matplotlib/axis.py

-1
Original file line numberDiff line numberDiff line change
@@ -597,7 +597,6 @@ class Ticker:
597597
formatter = None
598598

599599

600-
601600
class Axis(artist.Artist):
602601

603602
"""

lib/matplotlib/backends/__init__.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,6 @@ def do_nothing(*args, **kwargs): pass
5252

5353
matplotlib.verbose.report('backend %s version %s' % (backend,backend_version))
5454

55-
return new_figure_manager, draw_if_interactive, show
55+
return backend_mod, new_figure_manager, draw_if_interactive, show
5656

5757

lib/matplotlib/backends/backend_agg.py

+8-2
Original file line numberDiff line numberDiff line change
@@ -385,7 +385,6 @@ def post_processing(image, dpi):
385385
image)
386386

387387

388-
389388
def new_figure_manager(num, *args, **kwargs):
390389
"""
391390
Create a new figure manager instance
@@ -396,7 +395,14 @@ def new_figure_manager(num, *args, **kwargs):
396395

397396
FigureClass = kwargs.pop('FigureClass', Figure)
398397
thisFig = FigureClass(*args, **kwargs)
399-
canvas = FigureCanvasAgg(thisFig)
398+
return new_figure_manager_given_figure(num, thisFig)
399+
400+
401+
def new_figure_manager_given_figure(num, figure):
402+
"""
403+
Create a new figure manager instance for the given figure.
404+
"""
405+
canvas = FigureCanvasAgg(figure)
400406
manager = FigureManagerBase(canvas, num)
401407
return manager
402408

0 commit comments

Comments
 (0)