[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