Skip to content

Commit 01e916d

Browse files
authored
Merge pull request #23735 from QuLogic/fix-clear-cla
Correctly handle Axes subclasses that override cla
2 parents 54b2a63 + f569d1a commit 01e916d

File tree

3 files changed

+106
-9
lines changed

3 files changed

+106
-9
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
``AXes`` subclasses should override ``clear`` instead of ``cla``
2+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
3+
4+
For clarity, `.axes.Axes.clear` is now preferred over `.Axes.cla`. However, for
5+
backwards compatibility, the latter will remain as an alias for the former.
6+
7+
For additional compatibility with third-party libraries, Matplotlib will
8+
continue to call the ``cla`` method of any `~.axes.Axes` subclasses if they
9+
define it. In the future, this will no longer occur, and Matplotlib will only
10+
call the ``clear`` method in `~.axes.Axes` subclasses.
11+
12+
It is recommended to define only the ``clear`` method when on Matplotlib 3.6,
13+
and only ``cla`` for older versions.

lib/matplotlib/axes/_base.py

+39-6
Original file line numberDiff line numberDiff line change
@@ -559,6 +559,8 @@ class _AxesBase(martist.Artist):
559559
_shared_axes = {name: cbook.Grouper() for name in _axis_names}
560560
_twinned_axes = cbook.Grouper()
561561

562+
_subclass_uses_cla = False
563+
562564
@property
563565
def _axis_map(self):
564566
"""A mapping of axis names, e.g. 'x', to `Axis` instances."""
@@ -699,6 +701,20 @@ def __init__(self, fig, rect,
699701
rcParams['ytick.major.right']),
700702
which='major')
701703

704+
def __init_subclass__(cls, **kwargs):
705+
parent_uses_cla = super(cls, cls)._subclass_uses_cla
706+
if 'cla' in cls.__dict__:
707+
_api.warn_deprecated(
708+
'3.6',
709+
pending=True,
710+
message=f'Overriding `Axes.cla` in {cls.__qualname__} is '
711+
'pending deprecation in %(since)s and will be fully '
712+
'deprecated in favor of `Axes.clear` in the future. '
713+
'Please report '
714+
f'this to the {cls.__module__!r} author.')
715+
cls._subclass_uses_cla = 'cla' in cls.__dict__ or parent_uses_cla
716+
super().__init_subclass__(**kwargs)
717+
702718
def __getstate__(self):
703719
state = super().__getstate__()
704720
# Prune the sharing & twinning info to only contain the current group.
@@ -1199,9 +1215,12 @@ def sharey(self, other):
11991215
self.set_ylim(y0, y1, emit=False, auto=other.get_autoscaley_on())
12001216
self.yaxis._scale = other.yaxis._scale
12011217

1202-
def clear(self):
1218+
def __clear(self):
12031219
"""Clear the Axes."""
1204-
# Note: this is called by Axes.__init__()
1220+
# The actual implementation of clear() as long as clear() has to be
1221+
# an adapter delegating to the correct implementation.
1222+
# The implementation can move back into clear() when the
1223+
# deprecation on cla() subclassing expires.
12051224

12061225
# stash the current visibility state
12071226
if hasattr(self, 'patch'):
@@ -1318,6 +1337,24 @@ def clear(self):
13181337

13191338
self.stale = True
13201339

1340+
def clear(self):
1341+
"""Clear the Axes."""
1342+
# Act as an alias, or as the superclass implementation depending on the
1343+
# subclass implementation.
1344+
if self._subclass_uses_cla:
1345+
self.cla()
1346+
else:
1347+
self.__clear()
1348+
1349+
def cla(self):
1350+
"""Clear the Axes."""
1351+
# Act as an alias, or as the superclass implementation depending on the
1352+
# subclass implementation.
1353+
if self._subclass_uses_cla:
1354+
self.__clear()
1355+
else:
1356+
self.clear()
1357+
13211358
class ArtistList(MutableSequence):
13221359
"""
13231360
A sublist of Axes children based on their type.
@@ -1481,10 +1518,6 @@ def texts(self):
14811518
return self.ArtistList(self, 'texts', 'add_artist',
14821519
valid_types=mtext.Text)
14831520

1484-
def cla(self):
1485-
"""Clear the Axes."""
1486-
self.clear()
1487-
14881521
def get_facecolor(self):
14891522
"""Get the facecolor of the Axes."""
14901523
return self.patch.get_facecolor()

lib/matplotlib/tests/test_axes.py

+54-3
Original file line numberDiff line numberDiff line change
@@ -486,10 +486,61 @@ def test_inverted_cla():
486486
plt.close(fig)
487487

488488

489-
def test_cla_not_redefined():
489+
def test_subclass_clear_cla():
490+
# Ensure that subclasses of Axes call cla/clear correctly.
491+
# Note, we cannot use mocking here as we want to be sure that the
492+
# superclass fallback does not recurse.
493+
494+
with pytest.warns(match='Overriding `Axes.cla`'):
495+
class ClaAxes(Axes):
496+
def cla(self):
497+
nonlocal called
498+
called = True
499+
500+
with pytest.warns(match='Overriding `Axes.cla`'):
501+
class ClaSuperAxes(Axes):
502+
def cla(self):
503+
nonlocal called
504+
called = True
505+
super().cla()
506+
507+
class SubClaAxes(ClaAxes):
508+
pass
509+
510+
class ClearAxes(Axes):
511+
def clear(self):
512+
nonlocal called
513+
called = True
514+
515+
class ClearSuperAxes(Axes):
516+
def clear(self):
517+
nonlocal called
518+
called = True
519+
super().clear()
520+
521+
class SubClearAxes(ClearAxes):
522+
pass
523+
524+
fig = Figure()
525+
for axes_class in [ClaAxes, ClaSuperAxes, SubClaAxes,
526+
ClearAxes, ClearSuperAxes, SubClearAxes]:
527+
called = False
528+
ax = axes_class(fig, [0, 0, 1, 1])
529+
# Axes.__init__ has already called clear (which aliases to cla or is in
530+
# the subclass).
531+
assert called
532+
533+
called = False
534+
ax.cla()
535+
assert called
536+
537+
538+
def test_cla_not_redefined_internally():
490539
for klass in Axes.__subclasses__():
491-
# check that cla does not get redefined in our Axes subclasses
492-
assert 'cla' not in klass.__dict__
540+
# Check that cla does not get redefined in our Axes subclasses, except
541+
# for in the above test function.
542+
if 'test_subclass_clear_cla' not in klass.__qualname__:
543+
assert 'cla' not in klass.__dict__
493544

494545

495546
@check_figures_equal(extensions=["png"])

0 commit comments

Comments
 (0)