You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
[Bug]: Crash when removing a subfigure with a subplot in a figure #31319
The program crashes with "AttributeError: 'list' object has no attribute '_mouseover_set'" when I create a figure with subfigures, create subplots in the subfigures, and remove a subfigure.
File ~/dataviz-render-update-testing/matplotlib/lib/matplotlib/artist.py:260, in Artist.remove(self)
257 _ax_flag = False
258 if hasattr(self, 'axes') and self.axes:
259 # remove from the mouse hit list
--> 260 self.axes._mouseover_set.discard(self)
261 self.axes.stale = True
262 self.axes = None # decouple the artist from the Axes
AttributeError: 'list' object has no attribute '_mouseover_set'
The problem is that we everything inherits from Artist (including the Figure family of clases), but the type of obj.axes is not stable across all classes: for the Figure family it is list[Axes], for Axes it is None, and for everything else it is None | Axes [Yes this is non-ideal, no we would not do it this way if designing from scratch now, no it is not worth the deprecation cost to change it at this point]. Additionally we customize the remove functionality by setting a callback at run time. Thus, the assumption in the base Artist.remove is that if the artists have a remove callback set and the self.axes attribute is not falsey, then we are in the third category so we can treat the self.axes as an Axes object and need to clean up Axes stuff that was set in add_artist.
Looking at this I see a couple of paths:
update the _remove_method callback for sub-figures to recursively remove their axes/subfigures so that by the time we hit the code is Artist.removeself.axes == []
update Artist.remove to bail on self.axes being a list
update Artist.remove to bail on Figure subclasses of self
update Artist.romove to only not bail on Axes of self.axes
add a custom remove method someplace in the Figure family
None of these are super great, but I think overriding the remove method is the worst choice (mixing methods of injecting logic always makes things worse). Making sure SubFigures recursively clean them selves will make removing them a bit more expensive and will complicate some future plans but might help with releasing memory (breaks reference cycles) and is more consistent with the current behavior else where (e.g. this code block where we are making sure that for a data artist (Line2D) that is being removed from an Axes that they get fully decoupled). Changing the test from the truthyness of self.axes to a type check + not None check is probably the minimal change that will fix the problem with the fewest side effects.
Activity
tacaswell commentedon Mar 17, 2026
The problem is that we everything inherits from
Artist(including theFigurefamily of clases), but the type ofobj.axesis not stable across all classes: for the Figure family it islist[Axes], forAxesit isNone, and for everything else it isNone | Axes[Yes this is non-ideal, no we would not do it this way if designing from scratch now, no it is not worth the deprecation cost to change it at this point]. Additionally we customize theremovefunctionality by setting a callback at run time. Thus, the assumption in the baseArtist.removeis that if the artists have a remove callback set and theself.axesattribute is not falsey, then we are in the third category so we can treat theself.axesas an Axes object and need to clean upAxesstuff that was set inadd_artist.Looking at this I see a couple of paths:
_remove_methodcallback for sub-figures to recursively remove their axes/subfigures so that by the time we hit the code isArtist.removeself.axes == []Artist.removeto bail on self.axes being a listArtist.removeto bail on Figure subclasses of selfArtist.romoveto only not bail on Axes of self.axesremovemethod someplace in the Figure familyNone of these are super great, but I think overriding the
removemethod is the worst choice (mixing methods of injecting logic always makes things worse). Making sureSubFiguresrecursively clean them selves will make removing them a bit more expensive and will complicate some future plans but might help with releasing memory (breaks reference cycles) and is more consistent with the current behavior else where (e.g. this code block where we are making sure that for a data artist (Line2D) that is being removed from an Axes that they get fully decoupled). Changing the test from the truthyness ofself.axesto a type check + not None check is probably the minimal change that will fix the problem with the fewest side effects.QuLogic commentedon Mar 17, 2026
Oh, that's unfortunate. We actually have a test for
SubFigureremoval, but it's an empty one:matplotlib/lib/matplotlib/tests/test_figure.py
Lines 1575 to 1579 in dfa19f6
Fix AttributeError in artist.remove for SubFigures (matplotlib#31319)