Skip to content

Using matplotlib 2.1.0rc1 seems to corrupt PySide #9162

Closed

Description

@vallsv

Bug report

Bug summary

Hi. We are using CI of our project with PySide and pre releases of matplotlib. We notice that since 2.1.0rc1 all our tests fails with this kind or error message:

TypeError: PySide.QtCore.QObject isn't a direct base class of ClassBlahblahblah

After introspection we think that the problem come from matplotlib 2.1.0rc1, but it was too difficult to identify why. Using the previour version of matplotlib does not occur any problem.

The problem looks critical.

Any idea why this issue happens?

Code for reproduction

import logging
logging.basicConfig()
from PySide import QtGui

logger = logging.getLogger("test")

app = QtGui.QApplication([])

class Foo_super(QtGui.QWidget):
    def __init__(self, parent=None):
        super(Foo_super, self).__init__(parent)

class Foo_oldschool(QtGui.QWidget):
    def __init__(self, parent=None):
        QtGui.QWidget.__init__(self, parent)

import matplotlib
matplotlib.use('Qt4Agg')
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.figure import Figure

fig = Figure(figsize=(600,600), dpi=72, facecolor=(1,1,1), edgecolor=(0,0,0))
ax = fig.add_subplot(111)
ax.plot([0,1])

try:
    a = Foo_super()
except Exception as e:
    logger.error("Backtrace 1", exc_info=True)
try:
    a = Foo_oldschool()
except Exception as e:
    logger.error("Backtrace 2", exc_info=True)

canvas = FigureCanvas(fig)

try:
    a = Foo_super()
except Exception as e:
    logger.error("Backtrace 3", exc_info=True)
try:
    a = Foo_oldschool()
except Exception as e:
    logger.error("Backtrace 4", exc_info=True)

Actual outcome

ERROR:test:Backtrace 3
Traceback (most recent call last):
  File "test_matplotlib_vs_pyside.py", line 47, in <module>
    a = Foo_super()
  File "test_matplotlib_vs_pyside.py", line 14, in __init__
    super(Foo_super, self).__init__(parent)
TypeError: PySide.QtCore.QObject isn't a direct base class of Foo_super
ERROR:test:Backtrace 4
Traceback (most recent call last):
  File "test_matplotlib_vs_pyside.py", line 52, in <module>
    a = Foo_oldschool()
  File "test_matplotlib_vs_pyside.py", line 19, in __init__
    QtGui.QWidget.__init__(self, parent)
TypeError: PySide.QtCore.QObject isn't a direct base class of Foo_oldschool

Expected outcome

As you can see, after the use of canvas = FigureCanvas(fig), we are not able anymore to instantiate a very simple custom widget. Using old or new way to call inherited __init__ do not change anything.

Matplotlib version

Our CI is on Windows.

But I can reproduce it on my system with a fresh virtualenv, compiling everything using pip: numpy, matplotlib and PySide.

  • Operating System: Debian 8
  • Matplotlib Version: 2.1.0rc1
  • Python Version: Python 2.7.9
  • Other Libraries:
Package                            Version 
---------------------------------- --------
backports.functools-lru-cache      1.4     
backports.shutil-get-terminal-size 1.0.0   
cycler                             0.10.0  
decorator                          4.1.2   
enum34                             1.1.6   
ipython                            5.4.1   
ipython-genutils                   0.2.0   
matplotlib                         2.1.0rc1
numpy                              1.13.1  
pathlib2                           2.3.0   
pexpect                            4.2.1   
pickleshare                        0.7.4   
pip                                9.0.1   
prompt-toolkit                     1.0.15  
ptyprocess                         0.5.2   
Pygments                           2.2.0   
pyparsing                          2.2.0   
PySide                             1.2.4   
python-dateutil                    2.6.1   
pytz                               2017.2  
scandir                            1.5     
setuptools                         36.4.0  
simplegeneric                      0.8.1   
six                                1.10.0  
subprocess32                       3.2.7   
traitlets                          4.3.2   
wcwidth                            0.1.7   
wheel                              0.30.0a0
  • There is no problem with the same environment and using matplotlib 2.0.0 or 2.0.2.

Activity

  1. t20100 commented on Sep 6, 2017

    @t20100
    Contributor

    It looks to come from _allow_super_init which temporarily monkey-patch the QWidget.__init__
    https://github.com/matplotlib/matplotlib/blob/master/lib/matplotlib/backends/backend_qt5.py#L137
    and apparently does not undo the monkey-patch properly with PySide.

    From what I tested, replacing:

                    try:
                        # Restore __init__ to sip.simplewrapper.__init__.
                        del QtWidgets.QWidget.__init__
                    except AttributeError:
                        pass
    

    by QtWidgets.QWidget.__init__ = qwidget_init in the wrapper
    solves the issue and works for PySide (1.2.4) and PyQt4 (4.11.4) with python2.7 and PyQt5 (5.6) with python3.5 on Windows.
    Yet I'am not sure about side effects...

  2. changed the title [-]Using matplotlib 2.1.0rc1 looks to corrupt PySide[/-] [+]Using matplotlib 2.1.0rc1 seems to corrupt PySide[/+] on Sep 6, 2017
  3. added
    Release criticalFor bugs that make the library unusable (segfaults, incorrect plots, etc) and major regressions.
    on Sep 6, 2017
  4. added this to the milestone on Sep 6, 2017
  5. dopplershift commented on Sep 6, 2017

    @dopplershift
    Contributor

    attn: @anntzer

  6. anntzer commented on Sep 6, 2017

    @anntzer
    Contributor

    In my hand failure with pyside bisects back to #8394 and reverting it with

    diff --git a/lib/matplotlib/backends/backend_qt5.py b/lib/matplotlib/backends/backend_qt5.py
    index 1d3c7cd53..7ed83e295 100644
    --- a/lib/matplotlib/backends/backend_qt5.py
    +++ b/lib/matplotlib/backends/backend_qt5.py
    @@ -121,7 +121,7 @@ def _create_qApp():
                     if display is None or not re.search(r':\d', display):
                         raise RuntimeError('Invalid DISPLAY variable')
     
    -            qApp = QtWidgets.QApplication(["matplotlib"])
    +            qApp = QtWidgets.QApplication([])
                 qApp.lastWindowClosed.connect(qApp.quit)
             else:
                 qApp = app
    

    appears to fix the issue (although it would be nice to know why).

    I was going to say we definitely need to test pyside too, but right now the interactive backend mini-test-suite is Py3 only and this issue appears to be Py2 only.

  7. anntzer commented on Sep 6, 2017

    @anntzer
    Contributor

    Looks like the first thing to do is actually

    diff --git a/lib/matplotlib/backends/backend_qt5.py b/lib/matplotlib/backends/backend_qt5.py
    index 1d3c7cd53..8536313a6 100644
    --- a/lib/matplotlib/backends/backend_qt5.py
    +++ b/lib/matplotlib/backends/backend_qt5.py
    @@ -121,7 +121,7 @@ def _create_qApp():
                     if display is None or not re.search(r':\d', display):
                         raise RuntimeError('Invalid DISPLAY variable')
     
    -            qApp = QtWidgets.QApplication(["matplotlib"])
    +            qApp = QtWidgets.QApplication([b"matplotlib"])
                 qApp.lastWindowClosed.connect(qApp.quit)
             else:
                 qApp = app
    

    to fix https://bugreports.qt.io/browse/PYSIDE-75 -- this appears to also work with pyqt5-py3. I haven't tried all the combos but it does make sense to pass bytes as argv anyways.

    This does not fix the issue with the example code by the OP though (which is not subject to this bug as they explicitly create the QApp themselves).

  8. anntzer commented on Sep 6, 2017

    @anntzer
    Contributor

    And the patch proposed by the OP also looks fine to me.

  9. t20100 commented on Sep 7, 2017

    @t20100
    Contributor

    This patch also solves the issue and seems to work for what I tested (PySide, PyQt4 on python2, PyQt5 (5.6 and 5.9) on python3 on Windows).
    It avoids monkey-patching QWidget.__init__.

    --- a/lib/matplotlib/backends/backend_qt5.py
    +++ b/lib/matplotlib/backends/backend_qt5.py
    @@ -233,12 +233,16 @@ class FigureCanvasQT(QtWidgets.QWidget, FigureCanvasBase):
             dpi = self._dpi_ratio * self.figure._original_dpi
             self.figure._set_dpi(dpi, forward=False)
    
    -    @_allow_super_init
         def __init__(self, figure):
             _create_qApp()
             figure._original_dpi = figure.dpi
    
    -        super(FigureCanvasQT, self).__init__(figure=figure)
    +        if QT_API == "PyQt5":
    +            super(FigureCanvasQT, self).__init__(figure=figure)
    +
    +        else:
    +            QtWidgets.QWidget.__init__(self)
    +            FigureCanvasBase.__init__(self, figure=figure)
    
             self.figure = figure
             self._update_figure_dpi()
    

    (patch updated after more testing)

    If you are happy with this patch or the previous one, I can test further and make a PR.

  10. anntzer commented on Sep 7, 2017

    @anntzer
    Contributor

    See #9040 for why we switched to using super throughout. Please check whether your patch is subject to the same issue (I can't tell).

  11. t20100 commented on Sep 8, 2017

    @t20100
    Contributor

    I could not reproduced #9040 with both patches.
    (Tested on Debian8 with python 2.7 and 3.4 with PySide, PyQt4 and PyQt5.. not tested with PySide2).
    By decorating __init__ as in #9040, I also checked that the second patch was not calling multiple times the __init__.

    In #8618, a patch similar to the second one I proposed was already proposed but the if in the __init__ was apparently disliked (in the end this if ends up in the _allow_super_init).

    I made 2 branches with the two patches:

  12. dopplershift commented on Sep 18, 2017

    @dopplershift
    Contributor

    Closed by #9169.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Release criticalFor bugs that make the library unusable (segfaults, incorrect plots, etc) and major regressions.

    Type

    No type

    Projects

    No projects

      Milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions