Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 464dcf6

Browse files
authored
Merge pull request #19033 from anntzer/class-factories
Templatize class factories.
2 parents 401aa2f + 15f3b92 commit 464dcf6

File tree

8 files changed

+108
-162
lines changed

8 files changed

+108
-162
lines changed
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
The private ``matplotlib.axes._subplots._subplot_classes`` dict has been removed
2+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
3+
4+
Support for passing ``None`` to ``subplot_class_factory`` has been removed
5+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
6+
Explicitly pass in the base `~matplotlib.axes.Axes` class instead.

doc/missing-references.json

Lines changed: 32 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@
2020
"doc/users/prev_whats_new/whats_new_3.1.0.rst:335"
2121
],
2222
"cbar_axes": [
23-
"lib/mpl_toolkits/axes_grid1/axes_grid.py:docstring of mpl_toolkits.axes_grid1.axes_grid.ImageGrid.__init__:45",
2423
"lib/mpl_toolkits/axes_grid1/axes_grid.py:docstring of mpl_toolkits.axes_grid1.axes_grid.ImageGrid:45",
2524
"lib/mpl_toolkits/axisartist/axes_grid.py:docstring of mpl_toolkits.axisartist.axes_grid.ImageGrid:45"
2625
],
@@ -79,39 +78,35 @@
7978
],
8079
"matplotlib.axes.Axes.lines": [
8180
"doc/tutorials/intermediate/artists.rst:100",
82-
"doc/tutorials/intermediate/artists.rst:440"
81+
"doc/tutorials/intermediate/artists.rst:442"
8382
],
8483
"matplotlib.axes.Axes.patch": [
8584
"doc/api/prev_api_changes/api_changes_0.98.x.rst:89",
86-
"doc/tutorials/intermediate/artists.rst:186",
87-
"doc/tutorials/intermediate/artists.rst:424"
85+
"doc/tutorials/intermediate/artists.rst:187",
86+
"doc/tutorials/intermediate/artists.rst:426"
8887
],
8988
"matplotlib.axes.Axes.patches": [
90-
"doc/tutorials/intermediate/artists.rst:463"
89+
"doc/tutorials/intermediate/artists.rst:465"
9190
],
9291
"matplotlib.axes.Axes.transAxes": [
93-
"lib/mpl_toolkits/axes_grid1/anchored_artists.py:docstring of mpl_toolkits.axes_grid1.anchored_artists.AnchoredDirectionArrows.__init__:8",
9492
"lib/mpl_toolkits/axes_grid1/anchored_artists.py:docstring of mpl_toolkits.axes_grid1.anchored_artists.AnchoredDirectionArrows:8"
9593
],
9694
"matplotlib.axes.Axes.transData": [
97-
"lib/mpl_toolkits/axes_grid1/anchored_artists.py:docstring of mpl_toolkits.axes_grid1.anchored_artists.AnchoredAuxTransformBox.__init__:11",
9895
"lib/mpl_toolkits/axes_grid1/anchored_artists.py:docstring of mpl_toolkits.axes_grid1.anchored_artists.AnchoredAuxTransformBox:11",
99-
"lib/mpl_toolkits/axes_grid1/anchored_artists.py:docstring of mpl_toolkits.axes_grid1.anchored_artists.AnchoredEllipse.__init__:8",
10096
"lib/mpl_toolkits/axes_grid1/anchored_artists.py:docstring of mpl_toolkits.axes_grid1.anchored_artists.AnchoredEllipse:8",
101-
"lib/mpl_toolkits/axes_grid1/anchored_artists.py:docstring of mpl_toolkits.axes_grid1.anchored_artists.AnchoredSizeBar.__init__:8",
10297
"lib/mpl_toolkits/axes_grid1/anchored_artists.py:docstring of mpl_toolkits.axes_grid1.anchored_artists.AnchoredSizeBar:8"
10398
],
10499
"matplotlib.axes.Axes.viewLim": [
105100
"doc/api/prev_api_changes/api_changes_0.99.x.rst:23"
106101
],
107102
"matplotlib.axes.Axes.xaxis": [
108-
"doc/tutorials/intermediate/artists.rst:608"
103+
"doc/tutorials/intermediate/artists.rst:610"
109104
],
110105
"matplotlib.axes.Axes.yaxis": [
111-
"doc/tutorials/intermediate/artists.rst:608"
106+
"doc/tutorials/intermediate/artists.rst:610"
112107
],
113108
"matplotlib.axis.Axis.label": [
114-
"doc/tutorials/intermediate/artists.rst:655"
109+
"doc/tutorials/intermediate/artists.rst:657"
115110
],
116111
"matplotlib.cm.ScalarMappable.callbacksSM": [
117112
"doc/api/prev_api_changes/api_changes_0.98.0.rst:10"
@@ -124,11 +119,11 @@
124119
],
125120
"matplotlib.figure.Figure.patch": [
126121
"doc/api/prev_api_changes/api_changes_0.98.x.rst:89",
127-
"doc/tutorials/intermediate/artists.rst:186",
128-
"doc/tutorials/intermediate/artists.rst:319"
122+
"doc/tutorials/intermediate/artists.rst:187",
123+
"doc/tutorials/intermediate/artists.rst:320"
129124
],
130125
"matplotlib.figure.Figure.transFigure": [
131-
"doc/tutorials/intermediate/artists.rst:368"
126+
"doc/tutorials/intermediate/artists.rst:369"
132127
],
133128
"max": [
134129
"lib/matplotlib/transforms.py:docstring of matplotlib.transforms.Bbox.p1:4"
@@ -256,15 +251,6 @@
256251
"doc/tutorials/intermediate/artists.rst:44",
257252
"doc/tutorials/intermediate/artists.rst:67"
258253
],
259-
"matplotlib.axes._axes.Axes": [
260-
"doc/api/artist_api.rst:189",
261-
"doc/api/axes_api.rst:609",
262-
"lib/matplotlib/projections/geo.py:docstring of matplotlib.projections.geo.GeoAxes:1",
263-
"lib/matplotlib/projections/polar.py:docstring of matplotlib.projections.polar.PolarAxes:1",
264-
"lib/mpl_toolkits/axes_grid1/mpl_axes.py:docstring of mpl_toolkits.axes_grid1.mpl_axes.Axes:1",
265-
"lib/mpl_toolkits/axisartist/axislines.py:docstring of mpl_toolkits.axisartist.axislines.Axes:1",
266-
"lib/mpl_toolkits/mplot3d/axes3d.py:docstring of mpl_toolkits.mplot3d.axes3d.Axes3D:1"
267-
],
268254
"matplotlib.axes._base._AxesBase": [
269255
"doc/api/artist_api.rst:189",
270256
"doc/api/axes_api.rst:609",
@@ -428,16 +414,6 @@
428414
"lib/matplotlib/transforms.py:docstring of matplotlib.transforms.BlendedAffine2D:1",
429415
"lib/matplotlib/transforms.py:docstring of matplotlib.transforms.BlendedGenericTransform:1"
430416
],
431-
"matplotlib.tri.trifinder.TriFinder": [
432-
"lib/matplotlib/tri/trifinder.py:docstring of matplotlib.tri.trifinder.TrapezoidMapTriFinder:1"
433-
],
434-
"matplotlib.tri.triinterpolate.TriInterpolator": [
435-
"lib/matplotlib/tri/triinterpolate.py:docstring of matplotlib.tri.triinterpolate.CubicTriInterpolator:1",
436-
"lib/matplotlib/tri/triinterpolate.py:docstring of matplotlib.tri.triinterpolate.LinearTriInterpolator:1"
437-
],
438-
"matplotlib.tri.trirefine.TriRefiner": [
439-
"lib/matplotlib/tri/trirefine.py:docstring of matplotlib.tri.trirefine.UniformTriRefiner:1"
440-
],
441417
"matplotlib.widgets._SelectorWidget": [
442418
"lib/matplotlib/widgets.py:docstring of matplotlib.widgets.LassoSelector:1",
443419
"lib/matplotlib/widgets.py:docstring of matplotlib.widgets.PolygonSelector:1",
@@ -485,7 +461,7 @@
485461
"lib/mpl_toolkits/axisartist/axislines.py:docstring of mpl_toolkits.axisartist.axislines.AxisArtistHelper.Fixed:1",
486462
"lib/mpl_toolkits/axisartist/axislines.py:docstring of mpl_toolkits.axisartist.axislines.AxisArtistHelper.Floating:1"
487463
],
488-
"mpl_toolkits.axisartist.floating_axes.Floating AxesHostAxes": [
464+
"mpl_toolkits.axisartist.floating_axes.FloatingAxesHostAxes": [
489465
"<unknown>:1",
490466
"doc/api/_as_gen/mpl_toolkits.axisartist.floating_axes.rst:31:<autosummary>:1"
491467
],
@@ -498,11 +474,11 @@
498474
},
499475
"py:data": {
500476
"matplotlib.axes.Axes.transAxes": [
501-
"lib/matplotlib/axes/_axes.py:docstring of matplotlib.axes._axes.Axes.legend:219",
502-
"lib/matplotlib/figure.py:docstring of matplotlib.figure.FigureBase.legend:220",
503-
"lib/matplotlib/legend.py:docstring of matplotlib.legend.Legend:179",
504-
"lib/matplotlib/pyplot.py:docstring of matplotlib.pyplot.figlegend:220",
505-
"lib/matplotlib/pyplot.py:docstring of matplotlib.pyplot.legend:219"
477+
"lib/matplotlib/axes/_axes.py:docstring of matplotlib.axes._axes.Axes.legend:222",
478+
"lib/matplotlib/figure.py:docstring of matplotlib.figure.FigureBase.legend:223",
479+
"lib/matplotlib/legend.py:docstring of matplotlib.legend.Legend:182",
480+
"lib/matplotlib/pyplot.py:docstring of matplotlib.pyplot.figlegend:223",
481+
"lib/matplotlib/pyplot.py:docstring of matplotlib.pyplot.legend:222"
506482
]
507483
},
508484
"py:func": {
@@ -541,15 +517,13 @@
541517
],
542518
"_iter_collection": [
543519
"lib/matplotlib/backend_bases.py:docstring of matplotlib.backend_bases.RendererBase.draw_path_collection:12",
544-
"lib/matplotlib/backends/backend_agg.py:docstring of matplotlib.backends.backend_agg.RendererAgg.draw_path_collection:12",
545520
"lib/matplotlib/backends/backend_pdf.py:docstring of matplotlib.backends.backend_pdf.RendererPdf.draw_path_collection:12",
546521
"lib/matplotlib/backends/backend_ps.py:docstring of matplotlib.backends.backend_ps.RendererPS.draw_path_collection:12",
547522
"lib/matplotlib/backends/backend_svg.py:docstring of matplotlib.backends.backend_svg.RendererSVG.draw_path_collection:12",
548523
"lib/matplotlib/patheffects.py:docstring of matplotlib.patheffects.PathEffectRenderer.draw_path_collection:12"
549524
],
550525
"_iter_collection_raw_paths": [
551526
"lib/matplotlib/backend_bases.py:docstring of matplotlib.backend_bases.RendererBase.draw_path_collection:12",
552-
"lib/matplotlib/backends/backend_agg.py:docstring of matplotlib.backends.backend_agg.RendererAgg.draw_path_collection:12",
553527
"lib/matplotlib/backends/backend_pdf.py:docstring of matplotlib.backends.backend_pdf.RendererPdf.draw_path_collection:12",
554528
"lib/matplotlib/backends/backend_ps.py:docstring of matplotlib.backends.backend_ps.RendererPS.draw_path_collection:12",
555529
"lib/matplotlib/backends/backend_svg.py:docstring of matplotlib.backends.backend_svg.RendererSVG.draw_path_collection:12",
@@ -574,24 +548,22 @@
574548
"lib/matplotlib/transforms.py:docstring of matplotlib.transforms.Affine2DBase:13"
575549
],
576550
"matplotlib.collections._CollectionWithSizes.set_sizes": [
577-
"lib/matplotlib/axes/_axes.py:docstring of matplotlib.axes._axes.Axes.barbs:172",
578-
"lib/matplotlib/axes/_axes.py:docstring of matplotlib.axes._axes.Axes.broken_barh:82",
579-
"lib/matplotlib/axes/_axes.py:docstring of matplotlib.axes._axes.Axes.fill_between:112",
580-
"lib/matplotlib/axes/_axes.py:docstring of matplotlib.axes._axes.Axes.fill_betweenx:112",
581-
"lib/matplotlib/axes/_axes.py:docstring of matplotlib.axes._axes.Axes.hexbin:168",
582-
"lib/matplotlib/axes/_axes.py:docstring of matplotlib.axes._axes.Axes.pcolor:165",
583-
"lib/matplotlib/axes/_axes.py:docstring of matplotlib.axes._axes.Axes.quiver:202",
584-
"lib/matplotlib/pyplot.py:docstring of matplotlib.pyplot.barbs:172",
585-
"lib/matplotlib/pyplot.py:docstring of matplotlib.pyplot.broken_barh:82",
586-
"lib/matplotlib/pyplot.py:docstring of matplotlib.pyplot.fill_between:112",
587-
"lib/matplotlib/pyplot.py:docstring of matplotlib.pyplot.fill_betweenx:112",
588-
"lib/matplotlib/pyplot.py:docstring of matplotlib.pyplot.hexbin:168",
589-
"lib/matplotlib/pyplot.py:docstring of matplotlib.pyplot.pcolor:165",
590-
"lib/matplotlib/pyplot.py:docstring of matplotlib.pyplot.quiver:202",
591-
"lib/matplotlib/quiver.py:docstring of matplotlib.quiver.Barbs.__init__:176",
592-
"lib/matplotlib/quiver.py:docstring of matplotlib.quiver.Barbs:206",
593-
"lib/matplotlib/quiver.py:docstring of matplotlib.quiver.Quiver.__init__:206",
594-
"lib/matplotlib/quiver.py:docstring of matplotlib.quiver.Quiver:239"
551+
"lib/matplotlib/axes/_axes.py:docstring of matplotlib.axes._axes.Axes.barbs:171",
552+
"lib/matplotlib/axes/_axes.py:docstring of matplotlib.axes._axes.Axes.broken_barh:81",
553+
"lib/matplotlib/axes/_axes.py:docstring of matplotlib.axes._axes.Axes.fill_between:111",
554+
"lib/matplotlib/axes/_axes.py:docstring of matplotlib.axes._axes.Axes.fill_betweenx:111",
555+
"lib/matplotlib/axes/_axes.py:docstring of matplotlib.axes._axes.Axes.hexbin:167",
556+
"lib/matplotlib/axes/_axes.py:docstring of matplotlib.axes._axes.Axes.pcolor:164",
557+
"lib/matplotlib/axes/_axes.py:docstring of matplotlib.axes._axes.Axes.quiver:201",
558+
"lib/matplotlib/pyplot.py:docstring of matplotlib.pyplot.barbs:171",
559+
"lib/matplotlib/pyplot.py:docstring of matplotlib.pyplot.broken_barh:81",
560+
"lib/matplotlib/pyplot.py:docstring of matplotlib.pyplot.fill_between:111",
561+
"lib/matplotlib/pyplot.py:docstring of matplotlib.pyplot.fill_betweenx:111",
562+
"lib/matplotlib/pyplot.py:docstring of matplotlib.pyplot.hexbin:167",
563+
"lib/matplotlib/pyplot.py:docstring of matplotlib.pyplot.pcolor:164",
564+
"lib/matplotlib/pyplot.py:docstring of matplotlib.pyplot.quiver:201",
565+
"lib/matplotlib/quiver.py:docstring of matplotlib.quiver.Barbs:205",
566+
"lib/matplotlib/quiver.py:docstring of matplotlib.quiver.Quiver:238"
595567
],
596568
"matplotlib.dates.DateFormatter.__call__": [
597569
"doc/users/prev_whats_new/whats_new_1.5.rst:497"
@@ -727,7 +699,6 @@
727699
"doc/users/event_handling.rst:179"
728700
],
729701
"Size.from_any": [
730-
"lib/mpl_toolkits/axes_grid1/axes_grid.py:docstring of mpl_toolkits.axes_grid1.axes_grid.ImageGrid.__init__:57",
731702
"lib/mpl_toolkits/axes_grid1/axes_grid.py:docstring of mpl_toolkits.axes_grid1.axes_grid.ImageGrid:57",
732703
"lib/mpl_toolkits/axisartist/axes_grid.py:docstring of mpl_toolkits.axisartist.axes_grid.ImageGrid:57"
733704
],

lib/matplotlib/axes/_subplots.py

Lines changed: 3 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
1-
import functools
2-
3-
from matplotlib import _api
1+
from matplotlib import _api, cbook
42
from matplotlib.axes._axes import Axes
53
from matplotlib.gridspec import GridSpec, SubplotSpec
64

@@ -36,15 +34,6 @@ def __init__(self, fig, *args, **kwargs):
3634
# This will also update the axes position.
3735
self.set_subplotspec(SubplotSpec._from_subplot_args(fig, args))
3836

39-
def __reduce__(self):
40-
# get the first axes class which does not inherit from a subplotbase
41-
axes_class = next(
42-
c for c in type(self).__mro__
43-
if issubclass(c, Axes) and not issubclass(c, SubplotBase))
44-
return (_picklable_subplot_class_constructor,
45-
(axes_class,),
46-
self.__getstate__())
47-
4837
@_api.deprecated(
4938
"3.4", alternative="get_subplotspec",
5039
addendum="(get_subplotspec returns a SubplotSpec instance.)")
@@ -169,53 +158,6 @@ def _make_twin_axes(self, *args, **kwargs):
169158
return twin
170159

171160

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

lib/matplotlib/cbook/__init__.py

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2188,3 +2188,53 @@ def _unikey_or_keysym_to_mplkey(unikey, keysym):
21882188
"next": "pagedown", # Used by tk.
21892189
}.get(key, key)
21902190
return key
2191+
2192+
2193+
@functools.lru_cache(None)
2194+
def _make_class_factory(mixin_class, fmt, attr_name=None):
2195+
"""
2196+
Return a function that creates picklable classes inheriting from a mixin.
2197+
2198+
After ::
2199+
2200+
factory = _make_class_factory(FooMixin, fmt, attr_name)
2201+
FooAxes = factory(Axes)
2202+
2203+
``Foo`` is a class that inherits from ``FooMixin`` and ``Axes`` and **is
2204+
picklable** (picklability is what differentiates this from a plain call to
2205+
`type`). Its ``__name__`` is set to ``fmt.format(Axes.__name__)`` and the
2206+
base class is stored in the ``attr_name`` attribute, if not None.
2207+
2208+
Moreover, the return value of ``factory`` is memoized: calls with the same
2209+
``Axes`` class always return the same subclass.
2210+
"""
2211+
2212+
@functools.lru_cache(None)
2213+
def class_factory(axes_class):
2214+
# The parameter is named "axes_class" for backcompat but is really just
2215+
# a base class; no axes semantics are used.
2216+
base_class = axes_class
2217+
2218+
class subcls(mixin_class, base_class):
2219+
# Better approximation than __module__ = "matplotlib.cbook".
2220+
__module__ = mixin_class.__module__
2221+
2222+
def __reduce__(self):
2223+
return (_picklable_class_constructor,
2224+
(mixin_class, fmt, attr_name, base_class),
2225+
self.__getstate__())
2226+
2227+
subcls.__name__ = subcls.__qualname__ = fmt.format(base_class.__name__)
2228+
if attr_name is not None:
2229+
setattr(subcls, attr_name, base_class)
2230+
return subcls
2231+
2232+
class_factory.__module__ = mixin_class.__module__
2233+
return class_factory
2234+
2235+
2236+
def _picklable_class_constructor(mixin_class, fmt, attr_name, base_class):
2237+
"""Internal helper for _make_class_factory."""
2238+
factory = _make_class_factory(mixin_class, fmt, attr_name)
2239+
cls = factory(base_class)
2240+
return cls.__new__(cls)

lib/matplotlib/tests/test_axes.py

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -6340,21 +6340,6 @@ def test_spines_properbbox_after_zoom():
63406340
np.testing.assert_allclose(bb.get_points(), bb2.get_points(), rtol=1e-6)
63416341

63426342

6343-
def test_cartopy_backcompat():
6344-
6345-
class Dummy(matplotlib.axes.Axes):
6346-
...
6347-
6348-
class DummySubplot(matplotlib.axes.SubplotBase, Dummy):
6349-
_axes_class = Dummy
6350-
6351-
matplotlib.axes._subplots._subplot_classes[Dummy] = DummySubplot
6352-
6353-
FactoryDummySubplot = matplotlib.axes.subplot_class_factory(Dummy)
6354-
6355-
assert DummySubplot is FactoryDummySubplot
6356-
6357-
63586343
def test_gettightbbox_ignore_nan():
63596344
fig, ax = plt.subplots()
63606345
remove_ticks_and_titles(fig)

lib/matplotlib/tests/test_pickle.py

Lines 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

0 commit comments

Comments
 (0)