|
1 | 1 | import contextlib
|
2 |
| -from collections import namedtuple |
| 2 | +from collections import namedtuple, deque |
3 | 3 | import datetime
|
4 | 4 | from decimal import Decimal
|
5 | 5 | from functools import partial
|
| 6 | +import gc |
6 | 7 | import inspect
|
7 | 8 | import io
|
8 | 9 | from itertools import product
|
9 | 10 | import platform
|
| 11 | +import sys |
10 | 12 | from types import SimpleNamespace
|
11 | 13 |
|
12 | 14 | import dateutil.tz
|
|
23 | 25 | import matplotlib.dates as mdates
|
24 | 26 | from matplotlib.figure import Figure
|
25 | 27 | from matplotlib.axes import Axes
|
| 28 | +from matplotlib.lines import Line2D |
| 29 | +from matplotlib.collections import PathCollection |
26 | 30 | import matplotlib.font_manager as mfont_manager
|
27 | 31 | import matplotlib.markers as mmarkers
|
28 | 32 | import matplotlib.patches as mpatches
|
@@ -9209,6 +9213,49 @@ def test_axes_clear_behavior(fig_ref, fig_test, which):
|
9209 | 9213 | ax_test.grid(True)
|
9210 | 9214 |
|
9211 | 9215 |
|
| 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 | + |
9212 | 9259 | def test_boxplot_tick_labels():
|
9213 | 9260 | # Test the renamed `tick_labels` parameter.
|
9214 | 9261 | # Test for deprecation of old name `labels`.
|
|
0 commit comments