Skip to content

Conversation

@larrybradley
Copy link
Contributor

PR summary

In 3.11.0.dev, AxesWidget.__del__ is called on partially constructed selectors when __init__ raises early (e.g., EllipseSelector(ax, foo="bar") with an invalid kwarg). Because __init__ never runs, attributes like _blit_background_id and canvas are not set, so __del__ gives Exception ignored while calling deallocator <function AxesWidget.__del__ at 0x108598b40>: and raises an AttributeError during garbage collection. This surfaces as PytestUnraisableExceptionWarning in tests in downstream packages.
See, e.g., https://github.com/astropy/regions/actions/runs/20730257507/job/59516157314

This bug was introduced in #30591.

Minimal example (also added as a regression test):

import matplotlib.pyplot as plt
from matplotlib.widgets import EllipseSelector
fig, ax = plt.subplots()
try:
    EllipseSelector(ax, foo="bar")
except TypeError:
    pass
plt.close(fig)

PR checklist

Co-authored-by: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com>
@anntzer
Copy link
Contributor

anntzer commented Jan 12, 2026

Maybe instead of defining AxesWidget.__del__ at all it would be simpler to just register the necessary finalizer when the blitbackgroundid is first requested, i.e.

diff --git i/lib/matplotlib/widgets.py w/lib/matplotlib/widgets.py
index 5e87abe08d..9418794dcf 100644
--- i/lib/matplotlib/widgets.py
+++ w/lib/matplotlib/widgets.py
@@ -120,16 +120,6 @@ class AxesWidget(Widget):
         self._cids = []
         self._blit_background_id = None
 
-    def __del__(self):
-        blit_background_id = getattr(self, '_blit_background_id', None)
-        # __del__ may be called on a partially initialized object, e.g.,
-        # when __init__ raises. Therefore, we handle missing attributes
-        # gracefully.
-        if blit_background_id is not None:
-            canvas = getattr(self, 'canvas', None)
-            if canvas is not None:
-                canvas._release_blit_background_id(blit_background_id)
-
     canvas = property(
         lambda self: getattr(self.ax.get_figure(root=True), 'canvas', None)
     )
@@ -170,7 +160,10 @@ class AxesWidget(Widget):
         good enough for all existing widgets.
         """
         if self._blit_background_id is None:
-            self._blit_background_id = self.canvas._get_blit_background_id()
+            bbid = self.canvas._get_blit_background_id()
+            import weakref
+            weakref.finalize(self, self.canvas._release_blit_background_id, bbid)
+            self._blit_background_id = bbid
         self.canvas._blit_backgrounds[self._blit_background_id] = background
 
     def _load_blit_background(self):

?

@larrybradley
Copy link
Contributor Author

@anntzer Do you want me to implement your suggestion or was that a question for @timhoffm ?

@anntzer
Copy link
Contributor

anntzer commented Jan 22, 2026

If my patch works as is (well, moving the import to where it should be) I'd rather have that in directly, otherwise it can be punted.

@larrybradley
Copy link
Contributor Author

@anntzer OK, thanks. I've updated this PR with your suggested fix. The test passes.

@anntzer
Copy link
Contributor

anntzer commented Jan 23, 2026

Looks like the pyi file needs to be updated for the removal of __del__.

@larrybradley
Copy link
Contributor Author

@anntzer Fixed, thanks!

@QuLogic QuLogic added this to the v3.11.0 milestone Jan 24, 2026
@timhoffm timhoffm merged commit 74aa9ab into matplotlib:main Jan 24, 2026
37 of 40 checks passed
@larrybradley larrybradley deleted the fix-axeswidget-del branch January 24, 2026 14:45
llohse pushed a commit to llohse/matplotlib that referenced this pull request Feb 3, 2026
* FIX: Handle AxesWidget cleanup after failed init

* Apply code review suggestions

* Update lib/matplotlib/tests/test_widgets.py

Co-authored-by: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com>

* Remove __del__ method

* Remove AxesWidget.__del__ from widgets.pyi

---------

Co-authored-by: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com>
llohse pushed a commit to llohse/matplotlib that referenced this pull request Feb 3, 2026
* FIX: Handle AxesWidget cleanup after failed init

* Apply code review suggestions

* Update lib/matplotlib/tests/test_widgets.py

Co-authored-by: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com>

* Remove __del__ method

* Remove AxesWidget.__del__ from widgets.pyi

---------

Co-authored-by: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com>
llohse pushed a commit to llohse/matplotlib that referenced this pull request Feb 3, 2026
* FIX: Handle AxesWidget cleanup after failed init

* Apply code review suggestions

* Update lib/matplotlib/tests/test_widgets.py

Co-authored-by: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com>

* Remove __del__ method

* Remove AxesWidget.__del__ from widgets.pyi

---------

Co-authored-by: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants