Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit e6165b6

Browse filesBrowse files
committed
Templatize class factories.
This makes mpl_toolkits axes classes picklable (see test_pickle) by generalizing the machinery of _picklable_subplot_class_constructor, which would otherwise have had to be reimplemented for each class factory.
1 parent 5960b5a commit e6165b6
Copy full SHA for e6165b6

File tree

Expand file treeCollapse file tree

7 files changed

+68
-118
lines changed
Filter options
Expand file treeCollapse file tree

7 files changed

+68
-118
lines changed
+2Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
The private ``matplotlib.axes._subplots._subplot_classes`` dict has been removed
2+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

‎lib/matplotlib/axes/_subplots.py

Copy file name to clipboardExpand all lines: lib/matplotlib/axes/_subplots.py
+2-60Lines changed: 2 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import functools
21
import uuid
32

43
from matplotlib import _api, cbook, docstring
@@ -38,15 +37,6 @@ def __init__(self, fig, *args, **kwargs):
3837
# This will also update the axes position.
3938
self.set_subplotspec(SubplotSpec._from_subplot_args(fig, args))
4039

41-
def __reduce__(self):
42-
# get the first axes class which does not inherit from a subplotbase
43-
axes_class = next(
44-
c for c in type(self).__mro__
45-
if issubclass(c, Axes) and not issubclass(c, SubplotBase))
46-
return (_picklable_subplot_class_constructor,
47-
(axes_class,),
48-
self.__getstate__())
49-
5040
@_api.deprecated(
5141
"3.4", alternative="get_subplotspec",
5242
addendum="(get_subplotspec returns a SubplotSpec instance.)")
@@ -168,58 +158,10 @@ def _make_twin_axes(self, *args, **kwargs):
168158
return twin
169159

170160

171-
# this here to support cartopy which was using a private part of the
172-
# API to register their Axes subclasses.
173-
174-
# In 3.1 this should be changed to a dict subclass that warns on use
175-
# In 3.3 to a dict subclass that raises a useful exception on use
176-
# In 3.4 should be removed
177-
178-
# The slow timeline is to give cartopy enough time to get several
179-
# release out before we break them.
180-
_subplot_classes = {}
181-
182-
183-
@functools.lru_cache(None)
184-
def subplot_class_factory(axes_class=None):
185-
"""
186-
Make a new class that inherits from `.SubplotBase` and the
187-
given axes_class (which is assumed to be a subclass of `.axes.Axes`).
188-
This is perhaps a little bit roundabout to make a new class on
189-
the fly like this, but it means that a new Subplot class does
190-
not have to be created for every type of Axes.
191-
"""
192-
if axes_class is None:
193-
cbook.warn_deprecated(
194-
"3.3", message="Support for passing None to subplot_class_factory "
195-
"is deprecated since %(since)s; explicitly pass the default Axes "
196-
"class instead. This will become an error %(removal)s.")
197-
axes_class = Axes
198-
try:
199-
# Avoid creating two different instances of GeoAxesSubplot...
200-
# Only a temporary backcompat fix. This should be removed in
201-
# 3.4
202-
return next(cls for cls in SubplotBase.__subclasses__()
203-
if cls.__bases__ == (SubplotBase, axes_class))
204-
except StopIteration:
205-
return type("%sSubplot" % axes_class.__name__,
206-
(SubplotBase, axes_class),
207-
{'_axes_class': axes_class})
208-
209-
161+
subplot_class_factory = cbook._make_class_factory(
162+
SubplotBase, "{}Subplot", "_axes_class", default_axes_class=Axes)
210163
Subplot = subplot_class_factory(Axes) # Provided for backward compatibility.
211164

212-
213-
def _picklable_subplot_class_constructor(axes_class):
214-
"""
215-
Stub factory that returns an empty instance of the appropriate subplot
216-
class when called with an axes class. This is purely to allow pickling of
217-
Axes and Subplots.
218-
"""
219-
subplot_class = subplot_class_factory(axes_class)
220-
return subplot_class.__new__(subplot_class)
221-
222-
223165
docstring.interpd.update(Axes=martist.kwdoc(Axes))
224166
docstring.dedent_interpd(Axes.__init__)
225167

‎lib/matplotlib/cbook/__init__.py

Copy file name to clipboardExpand all lines: lib/matplotlib/cbook/__init__.py
+48Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2322,3 +2322,51 @@ def _unikey_or_keysym_to_mplkey(unikey, keysym):
23222322
"next": "pagedown", # Used by tk.
23232323
}.get(key, key)
23242324
return key
2325+
2326+
2327+
@functools.lru_cache(None)
2328+
def _make_class_factory(
2329+
mixin_class, fmt, axes_attr=None, *, default_axes_class=None):
2330+
"""
2331+
Return a function that creates picklable classes inheriting from a mixin.
2332+
2333+
After ::
2334+
2335+
factory = _make_class_factory(FooMixin, fmt, axes_attr)
2336+
FooAxes = factory(Axes)
2337+
2338+
``Foo`` is a class that inherits from ``FooMixin`` and ``Axes`` and **is
2339+
picklable** (picklability is what differentiates this from a plain call to
2340+
`type`). Its ``__name__`` is set to ``fmt.format(Axes.__name__)`` and the
2341+
base class is stored in the ``axes_attr`` attribute, if not None.
2342+
"""
2343+
2344+
@functools.lru_cache(None)
2345+
def class_factory(axes_class):
2346+
# default_axes_class should go away once the deprecation elapses.
2347+
if axes_class is None and default_axes_class is not None:
2348+
warn_deprecated(
2349+
"3.3", message="Support for passing None to class factories "
2350+
"is deprecated since %(since)s and will be removed "
2351+
"%(removal)s; explicitly pass the default Axes class instead.")
2352+
return class_factory(default_axes_class)
2353+
d = {"__reduce__":
2354+
lambda self: (_picklable_class_constructor,
2355+
(mixin_class, fmt, axes_attr,
2356+
default_axes_class, axes_class,),
2357+
self.__getstate__())}
2358+
if axes_attr is not None:
2359+
d[axes_attr] = axes_class
2360+
return type(
2361+
fmt.format(axes_class.__name__), (mixin_class, axes_class), d)
2362+
2363+
return class_factory
2364+
2365+
2366+
def _picklable_class_constructor(
2367+
base_cls, fmt, axes_attr, default_axes_class, axes_class):
2368+
"""Internal helper for _make_class_factory."""
2369+
cls = _make_class_factory(
2370+
base_cls, fmt, axes_attr,
2371+
default_axes_class=default_axes_class)(axes_class)
2372+
return cls.__new__(cls)

‎lib/matplotlib/tests/test_axes.py

Copy file name to clipboardExpand all lines: lib/matplotlib/tests/test_axes.py
-15Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -6142,21 +6142,6 @@ def test_spines_properbbox_after_zoom():
61426142
np.testing.assert_allclose(bb.get_points(), bb2.get_points(), rtol=1e-6)
61436143

61446144

6145-
def test_cartopy_backcompat():
6146-
6147-
class Dummy(matplotlib.axes.Axes):
6148-
...
6149-
6150-
class DummySubplot(matplotlib.axes.SubplotBase, Dummy):
6151-
_axes_class = Dummy
6152-
6153-
matplotlib.axes._subplots._subplot_classes[Dummy] = DummySubplot
6154-
6155-
FactoryDummySubplot = matplotlib.axes.subplot_class_factory(Dummy)
6156-
6157-
assert DummySubplot is FactoryDummySubplot
6158-
6159-
61606145
def test_gettightbbox_ignore_nan():
61616146
fig, ax = plt.subplots()
61626147
remove_ticks_and_titles(fig)

‎lib/matplotlib/tests/test_pickle.py

Copy file name to clipboardExpand all lines: lib/matplotlib/tests/test_pickle.py
+6Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import matplotlib.pyplot as plt
1111
import matplotlib.transforms as mtransforms
1212
import matplotlib.figure as mfigure
13+
from mpl_toolkits.axes_grid1 import parasite_axes
1314

1415

1516
def test_simple():
@@ -212,3 +213,8 @@ def test_unpickle_canvas():
212213
out.seek(0)
213214
fig2 = pickle.load(out)
214215
assert fig2.canvas is not None
216+
217+
218+
def test_mpl_toolkits():
219+
ax = parasite_axes.host_axes([0, 0, 1, 1])
220+
assert type(pickle.loads(pickle.dumps(ax))) == parasite_axes.HostAxes

‎lib/mpl_toolkits/axes_grid1/parasite_axes.py

Copy file name to clipboardExpand all lines: lib/mpl_toolkits/axes_grid1/parasite_axes.py
+7-34Lines changed: 7 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -84,20 +84,8 @@ def apply_aspect(self, position=None):
8484
# end of aux_transform support
8585

8686

87-
@functools.lru_cache(None)
88-
def parasite_axes_class_factory(axes_class=None):
89-
if axes_class is None:
90-
cbook.warn_deprecated(
91-
"3.3", message="Support for passing None to "
92-
"parasite_axes_class_factory is deprecated since %(since)s and "
93-
"will be removed %(removal)s; explicitly pass the default Axes "
94-
"class instead.")
95-
axes_class = Axes
96-
97-
return type("%sParasite" % axes_class.__name__,
98-
(ParasiteAxesBase, axes_class), {})
99-
100-
87+
parasite_axes_class_factory = cbook._make_class_factory(
88+
ParasiteAxesBase, "{}Parasite", default_axes_class=Axes)
10189
ParasiteAxes = parasite_axes_class_factory(Axes)
10290

10391

@@ -290,7 +278,7 @@ def _add_twin_axes(self, axes_class, **kwargs):
290278
*kwargs* are forwarded to the parasite axes constructor.
291279
"""
292280
if axes_class is None:
293-
axes_class = self._get_base_axes()
281+
axes_class = self._base_axes_class
294282
ax = parasite_axes_class_factory(axes_class)(self, **kwargs)
295283
self.parasites.append(ax)
296284
ax._remove_method = self._remove_any_twin
@@ -317,21 +305,10 @@ def get_tightbbox(self, renderer, call_axes_locator=True,
317305
return Bbox.union([b for b in bbs if b.width != 0 or b.height != 0])
318306

319307

320-
@functools.lru_cache(None)
321-
def host_axes_class_factory(axes_class=None):
322-
if axes_class is None:
323-
cbook.warn_deprecated(
324-
"3.3", message="Support for passing None to host_axes_class is "
325-
"deprecated since %(since)s and will be removed %(removed)s; "
326-
"explicitly pass the default Axes class instead.")
327-
axes_class = Axes
328-
329-
def _get_base_axes(self):
330-
return axes_class
331-
332-
return type("%sHostAxes" % axes_class.__name__,
333-
(HostAxesBase, axes_class),
334-
{'_get_base_axes': _get_base_axes})
308+
host_axes_class_factory = cbook._make_class_factory(
309+
HostAxesBase, "{}HostAxes", "_base_axes_class", default_axes_class=Axes)
310+
HostAxes = host_axes_class_factory(Axes)
311+
SubplotHost = subplot_class_factory(HostAxes)
335312

336313

337314
def host_subplot_class_factory(axes_class):
@@ -340,10 +317,6 @@ def host_subplot_class_factory(axes_class):
340317
return subplot_host_class
341318

342319

343-
HostAxes = host_axes_class_factory(Axes)
344-
SubplotHost = subplot_class_factory(HostAxes)
345-
346-
347320
def host_axes(*args, axes_class=Axes, figure=None, **kwargs):
348321
"""
349322
Create axes that can act as a hosts to parasitic axes.

‎lib/mpl_toolkits/axisartist/floating_axes.py

Copy file name to clipboardExpand all lines: lib/mpl_toolkits/axisartist/floating_axes.py
+3-9Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,9 @@
55
# TODO :
66
# see if tick_iterator method can be simplified by reusing the parent method.
77

8-
import functools
9-
108
import numpy as np
119

10+
from matplotlib import cbook
1211
import matplotlib.patches as mpatches
1312
from matplotlib.path import Path
1413
import matplotlib.axes as maxes
@@ -360,13 +359,8 @@ def adjust_axes_lim(self):
360359
self.set_ylim(ymin-dy, ymax+dy)
361360

362361

363-
@functools.lru_cache(None)
364-
def floatingaxes_class_factory(axes_class):
365-
return type("Floating %s" % axes_class.__name__,
366-
(FloatingAxesBase, axes_class),
367-
{'_axes_class_floating': axes_class})
368-
369-
362+
floatingaxes_class_factory = cbook._make_class_factory(
363+
FloatingAxesBase, "Floating{}", "_axes_class_floating")
370364
FloatingAxes = floatingaxes_class_factory(
371365
host_axes_class_factory(axislines.Axes))
372366
FloatingSubplot = maxes.subplot_class_factory(FloatingAxes)

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.