Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

signal.connect() TypeError in pyqt5 when signal has an argument #388

Open
eyeteajay opened this issue Jun 10, 2023 · 9 comments
Open

signal.connect() TypeError in pyqt5 when signal has an argument #388

eyeteajay opened this issue Jun 10, 2023 · 9 comments

Comments

@eyeteajay
Copy link

eyeteajay commented Jun 10, 2023

Using this code example:

import logging
from Qt import QtCore
from Qt import QtWidgets

class MainWind(QtWidgets.QMainWindow):
    willclose = QtCore.Signal(int)
    
    def __init__(self, *args, **kw):
        super(MainWind, self).__init__(*args, **kw)
        but = QtWidgets.QPushButton("hi")
        self.setCentralWidget(but)
    
    def closeEvent(self, evt):
        closecode = 10
        logging.info("will close '%s': %s: %s", self.objectName(), closecode, self)
        self.willclose.emit(closecode)
        return super(MainWind, self).closeEvent(evt)

class Runner(object):
    def __init__(self, wind):
        wind.willclose.connect(self.didclose)
        self._wind = wind
    
    @QtCore.Slot(int)
    def didclose(self, closecode):
        logging.info("didclose signal called with: %r", closecode)
    
    def run(self):
        self._wind.show()
        exitcode = QtWidgets.QApplication.instance().exec_()
        return exitcode

logging.basicConfig(level=logging.DEBUG)
app = QtWidgets.QApplication([])
wind = MainWind()
r = Runner(wind)
r.run()

produces a TypeError when trying to connect a signal using python3 & PyQt5:
TypeError: connect() failed between MainWind.willclose[int] and didclose()

However, the same code runs fine using python3 & PySide2.
An undecorated didclose() method will also run using PyQt5, and a slot method that takes no arguments will also run.

@eyeteajay
Copy link
Author

I don't think the os matters, but this was on a Mac. And the pip environments I tested with are:

Package       Version
------------- ----------
pip           22.3.1
PyQt5         5.15.9
PyQt5-Qt5     5.15.2
PyQt5-sip     12.12.1
Qt.py         1.3.8
setuptools    65.5.0
types-PySide2 5.15.2.1.5

vs.

pip           22.3.1
PySide2       5.13.2
Qt.py         1.3.8
setuptools    65.5.0
shiboken2     5.13.2
types-PySide2 5.15.2.1.5

@mottosso
Copy link
Owner

Thanks for reporting this. Does this happen when using PyQt5 directly, without Qt.py? Could this be a PyQt5 bug, rather than a Qt.py bug?

@MHendricks
Copy link
Collaborator

Looks like PyQt5 requires inheriting from QObject not python's object to use the Slot decorator. This code change fixes the original example, and will run for both PyQt5 and PySide2.

- class Runner(object):
+ class Runner(QtCore.QObject):
    def __init__(self, wind):
+         super().__init__()
        wind.willclose.connect(self.didclose)
        self._wind = wind

Looks like this is not a Qt.py issue but a low level difference in how PySide2 and PyQt5 work. I wonder if there are any sip.setapi features for PyQt5 that would address issues like this.

@mottosso
Copy link
Owner

Nice catch; yes I expected PySide to require this too. This is true for the C++ side too, so I would consider PySide to be at fault for allowing it.

Is there anything Qt.py can do to disallow it cross binding? (thinking) That could help users get the error early and on every binding.

@eyeteajay
Copy link
Author

@MHendricks thanks! I had tested it with QObject but I forgot to initialize it, so I thought it didn't matter in that case. It makes sense that it should require QObject, so I agree it's more Pyside's fault.

@eyeteajay
Copy link
Author

@mottosso I think there should be some decorator magic that could examine the mro of the class of the unbound method and see if it inherits from QObject? If I can come up with anything I'll share

@mottosso
Copy link
Owner

Great, then let's consider this issue fixed once we can prevent PySide from allowing signals in non-QObject classes.

@MHendricks
Copy link
Collaborator

I've found another inconsistency when creating a QApplication between PyQt5 and PySide2. PyQt5 requires passing a list, but PySide2 does not.

>>> from PySide2 import QtWidgets
>>> QtWidgets.QApplication()
<PySide2.QtWidgets.QApplication(0x23563d1ecc0) at 0x000002353342A0F0>
>>> exit()
>>> from PyQt5 import QtWidgets
>>> QtWidgets.QApplication()
Traceback (most recent call last):
 File "<stdin>", line 1, in <module>
TypeError: QApplication(argv: List[str]): not enough arguments
>>> QtWidgets.QApplication([])
<PyQt5.QtWidgets.QApplication object at 0x00000264E3274160>

@mottosso
Copy link
Owner

I've found another inconsistency when creating a QApplication between PyQt5 and PySide2. PyQt5 requires passing a list, but PySide2 does not.

Sounds like a good fit for another issue with a dedicated PR. I'd vouch to make PyQt5 be OK with no argument, by just subclassing it with our own wrapper. Up for it?

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

No branches or pull requests

3 participants