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

Skip to content

Commit 82acbd8

Browse files
Break Artist._remove_method reference cycle (#28861)
* Break Artist._remove_method reference cycle Co-authored-by: Elliott Sales de Andrade <[email protected]>
1 parent 235bf97 commit 82acbd8

File tree

2 files changed

+54
-2
lines changed

2 files changed

+54
-2
lines changed

lib/matplotlib/axes/_base.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1302,9 +1302,14 @@ def __clear(self):
13021302
self._get_patches_for_fill = _process_plot_var_args('fill')
13031303

13041304
self._gridOn = mpl.rcParams['axes.grid']
1305+
# Swap children to minimize time we spend in an invalid state
13051306
old_children, self._children = self._children, []
13061307
for chld in old_children:
1307-
chld.axes = chld._parent_figure = None
1308+
chld._remove_method = None
1309+
chld._parent_figure = None
1310+
chld.axes = None
1311+
# Use list.clear to break the `artist._remove_method` reference cycle
1312+
old_children.clear()
13081313
self._mouseover_set = _OrderedSet()
13091314
self.child_axes = []
13101315
self._current_image = None # strictly for pyplot via _sci, _gci

lib/matplotlib/tests/test_axes.py

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
import contextlib
2-
from collections import namedtuple
2+
from collections import namedtuple, deque
33
import datetime
44
from decimal import Decimal
55
from functools import partial
6+
import gc
67
import inspect
78
import io
89
from itertools import product
910
import platform
11+
import sys
1012
from types import SimpleNamespace
1113

1214
import dateutil.tz
@@ -23,6 +25,8 @@
2325
import matplotlib.dates as mdates
2426
from matplotlib.figure import Figure
2527
from matplotlib.axes import Axes
28+
from matplotlib.lines import Line2D
29+
from matplotlib.collections import PathCollection
2630
import matplotlib.font_manager as mfont_manager
2731
import matplotlib.markers as mmarkers
2832
import matplotlib.patches as mpatches
@@ -9209,6 +9213,49 @@ def test_axes_clear_behavior(fig_ref, fig_test, which):
92099213
ax_test.grid(True)
92109214

92119215

9216+
@pytest.mark.skipif(
9217+
sys.version_info[:3] == (3, 13, 0) and sys.version_info.releaselevel != "final",
9218+
reason="https://github.com/python/cpython/issues/124538",
9219+
)
9220+
def test_axes_clear_reference_cycle():
9221+
def assert_not_in_reference_cycle(start):
9222+
# Breadth first search. Return True if we encounter the starting node
9223+
to_visit = deque([start])
9224+
explored = set()
9225+
while len(to_visit) > 0:
9226+
parent = to_visit.popleft()
9227+
for child in gc.get_referents(parent):
9228+
if id(child) in explored:
9229+
continue
9230+
assert child is not start
9231+
explored.add(id(child))
9232+
to_visit.append(child)
9233+
9234+
fig = Figure()
9235+
ax = fig.add_subplot()
9236+
points = np.random.rand(1000)
9237+
ax.plot(points, points)
9238+
ax.scatter(points, points)
9239+
ax_children = ax.get_children()
9240+
fig.clear() # This should break the reference cycle
9241+
9242+
# Care most about the objects that scale with number of points
9243+
big_artists = [
9244+
a for a in ax_children
9245+
if isinstance(a, (Line2D, PathCollection))
9246+
]
9247+
assert len(big_artists) > 0
9248+
for big_artist in big_artists:
9249+
assert_not_in_reference_cycle(big_artist)
9250+
assert len(ax_children) > 0
9251+
for child in ax_children:
9252+
# Make sure this doesn't raise because the child is already removed.
9253+
try:
9254+
child.remove()
9255+
except NotImplementedError:
9256+
pass # not implemented is expected for some artists
9257+
9258+
92129259
def test_boxplot_tick_labels():
92139260
# Test the renamed `tick_labels` parameter.
92149261
# Test for deprecation of old name `labels`.

0 commit comments

Comments
 (0)