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

Skip to content

Break Artist._remove_method reference cycle #28861

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 13 commits into from
Oct 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion lib/matplotlib/axes/_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -1302,9 +1302,14 @@ def __clear(self):
self._get_patches_for_fill = _process_plot_var_args('fill')

self._gridOn = mpl.rcParams['axes.grid']
# Swap children to minimize time we spend in an invalid state
old_children, self._children = self._children, []
for chld in old_children:
chld.axes = chld._parent_figure = None
chld._remove_method = None
chld._parent_figure = None
chld.axes = None
# Use list.clear to break the `artist._remove_method` reference cycle
old_children.clear()
self._mouseover_set = _OrderedSet()
self.child_axes = []
self._current_image = None # strictly for pyplot via _sci, _gci
Expand Down
49 changes: 48 additions & 1 deletion lib/matplotlib/tests/test_axes.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import contextlib
from collections import namedtuple
from collections import namedtuple, deque
import datetime
from decimal import Decimal
from functools import partial
import gc
import inspect
import io
from itertools import product
import platform
import sys
from types import SimpleNamespace

import dateutil.tz
Expand All @@ -23,6 +25,8 @@
import matplotlib.dates as mdates
from matplotlib.figure import Figure
from matplotlib.axes import Axes
from matplotlib.lines import Line2D
from matplotlib.collections import PathCollection
import matplotlib.font_manager as mfont_manager
import matplotlib.markers as mmarkers
import matplotlib.patches as mpatches
Expand Down Expand Up @@ -9209,6 +9213,49 @@ def test_axes_clear_behavior(fig_ref, fig_test, which):
ax_test.grid(True)


@pytest.mark.skipif(
sys.version_info[:3] == (3, 13, 0) and sys.version_info.releaselevel != "final",
reason="https://github.com/python/cpython/issues/124538",
)
def test_axes_clear_reference_cycle():
def assert_not_in_reference_cycle(start):
# Breadth first search. Return True if we encounter the starting node
to_visit = deque([start])
explored = set()
while len(to_visit) > 0:
parent = to_visit.popleft()
for child in gc.get_referents(parent):
if id(child) in explored:
continue
assert child is not start
explored.add(id(child))
to_visit.append(child)

fig = Figure()
ax = fig.add_subplot()
points = np.random.rand(1000)
ax.plot(points, points)
ax.scatter(points, points)
ax_children = ax.get_children()
fig.clear() # This should break the reference cycle

# Care most about the objects that scale with number of points
big_artists = [
a for a in ax_children
if isinstance(a, (Line2D, PathCollection))
]
assert len(big_artists) > 0
for big_artist in big_artists:
assert_not_in_reference_cycle(big_artist)
assert len(ax_children) > 0
for child in ax_children:
# Make sure this doesn't raise because the child is already removed.
try:
child.remove()
except NotImplementedError:
pass # not implemented is expected for some artists


def test_boxplot_tick_labels():
# Test the renamed `tick_labels` parameter.
# Test for deprecation of old name `labels`.
Expand Down
Loading