[PyQt] Decorator weirdness
Phil Thompson
phil at riverbankcomputing.com
Sat Aug 15 15:02:02 BST 2015
On 15 Aug 2015, at 12:43 pm, Iosif Spulber <iosif.spulber at gmail.com> wrote:
>
> Hi,
>
> I am using PyQt4 4.10.4.
>
> I'm a bit puzzled by the pyqtSlot decorator. All I can find in the docs is that it potentially
> improves performance, but I'm witnessing actual different behaviour depending on how I use it.
>
> 1. The decorator seems to properly disconnect slots when the object is deleted.
>
> In the snippet below, a slot of a widget to be deleted is connected to a button's click.
> If I close the SlotWidget and click the button, without the decorator I get a:
> "RuntimeError: wrapped C/C++ object of type QLabel has been deleted".
> With the decorator, the signal is disconnected before the object is destroyed and the click
> has no effect.
>
> My conclusion thus is that it's quite important to decorate slots like this.
>
> from PyQt4 import QtGui, QtCore
>
> app = QtGui.QApplication([])
>
> class SlotWidget(QtGui.QWidget):
> def __init__(self, parent=None):
> QtGui.QWidget.__init__(self, parent)
>
> self.label = QtGui.QLabel('LABEL', parent=self)
> # Make sure the object is deleted so potential problems arise
> self.setAttribute(QtCore.Qt.WA_DeleteOnClose)
>
> # Without this decorator, when this widget is closed and the button is
> # clicked, you get a RuntimeError.
> @QtCore.pyqtSlot()
> def my_slot(self):
> print 'Label text: %s' % self.label.text()
>
> class MyWidget(QtGui.QWidget):
> def __init__(self, parent=None):
> QtGui.QWidget.__init__(self, parent)
>
> self.button = QtGui.QPushButton("Button", parent=self)
> self.slot_widget = SlotWidget(parent=None)
> self.slot_widget.show()
>
> self.button.clicked.connect(self.slot_widget.my_slot)
>
> if __name__ == "__main__":
> widget = MyWidget()
>
> widget.show()
> app.exec_()
Worth looking at http://pyqt.sourceforge.net/Docs/PyQt5/incompatibilities.html#pyqt-v5-3
PyQt4 implements the “old” behaviour.
> 2. Consider this mock example; here, I want to provide some base functionality for my widgets,
> that involves connecting a signal to a slot. I don't want to inherit from QObject since multiple
> inheritance from QObject creates all sorts of issues in PyQt4.
>
> What I notice is that decorating the slot leads to an error:
> "TypeError: connect() failed between timeout() and base_slot()".
> Even weirder, calling the Base constructor before the QWidget one ensures that the slot
> is called properly.
>
> So in this case it seems to be better not to decorate the slot, but it can lead to problems (see 1.).
>
> from PyQt4 import QtGui, QtCore
>
> app = QtGui.QApplication([])
>
> class Base(object):
> def __init__(self, timer):
> self.timer = timer
> self.timer.timeout.connect(self.base_slot)
>
> # Remove the decorator -> works regardless of __init__ order
> @QtCore.pyqtSlot()
> def base_slot(self):
> print 'Fired!’
PyQt4 does not support pyqtSlot() in non-QObject classes. PyQt5 does...
http://pyqt.sourceforge.net/Docs/PyQt5/pyqt4_differences.html#new-style-signals-and-slots
> class MyWidget(QtGui.QWidget, Base):
> def __init__(self, timer, parent=None):
> # Switch the order -> works regardless of decorator
> QtGui.QWidget.__init__(self, parent)
> Base.__init__(self, timer)
>
> self.button = QtGui.QPushButton("Button", parent=self)
> self.button.clicked.connect(self.derived_slot)
>
> @QtCore.pyqtSlot()
> def derived_slot(self):
> print 'Preparing to fire...'
> self.timer.start(2000)
>
> if __name__ == "__main__":
> timer = QtCore.QTimer()
> timer.setSingleShot(True)
>
> widget = MyWidget(timer)
>
> widget.show()
> app.exec_()
>
> 3. Are slots always supposed to be bound? (i.e., should all the slots have a self first argument?)
They just need to be callables.
> 4. I would expect that explicitly decorating the slot with (C++) types should provide enough
> information to disambiguate between overloaded signals. E.g., if I have a slot decorated with
> @QtCore.pyqtSlot(str) and connect it to QComboBox.activated it connects to the int signal
> by default (I have to connect to QComboBox.activated[str]).
Added to the PyQt5 TODO list (but not PyQt4).
Phil
More information about the PyQt
mailing list