diff --git a/lib/matplotlib/backends/backend_qt5.py b/lib/matplotlib/backends/backend_qt5.py index 2685ab5b677a..6fc86486183c 100644 --- a/lib/matplotlib/backends/backend_qt5.py +++ b/lib/matplotlib/backends/backend_qt5.py @@ -17,7 +17,8 @@ from matplotlib.backend_managers import ToolManager from .qt_compat import ( - QtCore, QtGui, QtWidgets, _getSaveFileName, is_pyqt5, __version__, QT_API) + QtCore, QtGui, QtWidgets, _isdeleted, _getSaveFileName, + is_pyqt5, __version__, QT_API) backend_version = __version__ @@ -200,6 +201,12 @@ def __init__(self, *args, **kwargs): self._timer.timeout.connect(self._on_timer) self._timer_set_interval() + def __del__(self): + # The check for deletedness is needed to avoid an error at animation + # shutdown with PySide2. + if not _isdeleted(self._timer): + self._timer_stop() + def _timer_set_single_shot(self): self._timer.setSingleShot(self._single) diff --git a/lib/matplotlib/backends/qt_compat.py b/lib/matplotlib/backends/qt_compat.py index 6b52284fe7f4..b3367ea91010 100644 --- a/lib/matplotlib/backends/qt_compat.py +++ b/lib/matplotlib/backends/qt_compat.py @@ -63,16 +63,21 @@ def _setup_pyqt5(): - global QtCore, QtGui, QtWidgets, __version__, is_pyqt5, _getSaveFileName + global QtCore, QtGui, QtWidgets, __version__, is_pyqt5, \ + _isdeleted, _getSaveFileName if QT_API == QT_API_PYQT5: from PyQt5 import QtCore, QtGui, QtWidgets + import sip __version__ = QtCore.PYQT_VERSION_STR QtCore.Signal = QtCore.pyqtSignal QtCore.Slot = QtCore.pyqtSlot QtCore.Property = QtCore.pyqtProperty + _isdeleted = sip.isdeleted elif QT_API == QT_API_PYSIDE2: from PySide2 import QtCore, QtGui, QtWidgets, __version__ + import shiboken2 + def _isdeleted(obj): return not shiboken2.isValid(obj) else: raise ValueError("Unexpected value for the 'backend.qt5' rcparam") _getSaveFileName = QtWidgets.QFileDialog.getSaveFileName @@ -82,7 +87,8 @@ def is_pyqt5(): def _setup_pyqt4(): - global QtCore, QtGui, QtWidgets, __version__, is_pyqt5, _getSaveFileName + global QtCore, QtGui, QtWidgets, __version__, is_pyqt5, \ + _isdeleted, _getSaveFileName def _setup_pyqt4_internal(api): global QtCore, QtGui, QtWidgets, \ @@ -102,6 +108,7 @@ def _setup_pyqt4_internal(api): except ValueError: pass from PyQt4 import QtCore, QtGui + import sip # Always succeeds *after* importing PyQt4. __version__ = QtCore.PYQT_VERSION_STR # PyQt 4.6 introduced getSaveFileNameAndFilter: # https://riverbankcomputing.com/news/pyqt-46 @@ -110,16 +117,19 @@ def _setup_pyqt4_internal(api): QtCore.Signal = QtCore.pyqtSignal QtCore.Slot = QtCore.pyqtSlot QtCore.Property = QtCore.pyqtProperty + _isdeleted = sip.isdeleted _getSaveFileName = QtGui.QFileDialog.getSaveFileNameAndFilter if QT_API == QT_API_PYQTv2: _setup_pyqt4_internal(api=2) elif QT_API == QT_API_PYSIDE: from PySide import QtCore, QtGui, __version__, __version_info__ + import shiboken # PySide 1.0.3 fixed the following: # https://srinikom.github.io/pyside-bz-archive/809.html if __version_info__ < (1, 0, 3): raise ImportError("PySide<1.0.3 is not supported") + def _isdeleted(obj): return not shiboken.isValid(obj) _getSaveFileName = QtGui.QFileDialog.getSaveFileName elif QT_API == QT_API_PYQT: _setup_pyqt4_internal(api=1)