3

I am a PyQt 5.4.1-1 beginner, my python is 3.4.3. Here is my attempt to follow the many blogposts and SO questions on the proper ™ way to make a thread (i.e. no QThread subclassing):

#!/usr/bin/env python3

from PyQt5.QtCore import QObject, QThread
from PyQt5.QtCore import pyqtSlot, pyqtSignal
from PyQt5.QtWidgets import QMainWindow

class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        print("Base init")

        self.thread = QThread()
        w = Worker()
        w.finished[int].connect(self.onFinished)
        w.moveToThread(self.thread)
        self.thread.started.connect(w.work)
        self.thread.start()

    @pyqtSlot(int)
    def onFinished(self, i):
        print("Base caught finished, {}".format(i))

class Worker(QObject):
    finished = pyqtSignal(int)

    def __init__(self):
        print("Worker init")
        super().__init__()

    def work(self):
        print("Worker work")
        Worker.finished.emit(42)

if __name__ == "__main__":
    import sys
    from PyQt5.QtWidgets import QApplication

    app = QApplication(sys.argv)
    mw = MainWindow()
    mw.show()

    sys.exit(app.exec_())

While writing this question, I realised that if the following changes are made, then it all seems to work:

...
self.w = Worker()
self.w.finished[int].connect(self.onFinished)
self.w.moveToThread(self.thread)
self.thread.started.connect(self.w.work)
...

...
self.finished.emit(42)
...

But I do not understand why this helps. Why do I have to make a non-Gui related Worker instance a member of the Gui class? Feels wrong, frankly. And now about the signal: it's a class attribute so why is there a difference between calling Worker.finished.emit(42) which fails at runtime and a self.finisehd.emit(42) which succeeds when I would expect no difference (it's a class attribute!)

Community
  • 1
  • 1
alisianoi
  • 2,003
  • 3
  • 31
  • 46

1 Answers1

1

First, Python is an automatically garbage collected language. Your variable w goes out of scope in the __init__ method, and is promptly garbage collected after the method returns. This is why things did not work out the way you expected the first time. Making the variable a member of the class makes sure that it does not go out of scope while the MainWindow instance exists.

Your second question is a bit harder to answer, but I bet you will gain valuable insight into how signals work in PySide/PyQt by looking at the QMetaObject documentation.

--EDIT--

Found a better answer for your second question here.

Community
  • 1
  • 1
nullstellensatz
  • 756
  • 8
  • 18
  • The Qt docs won't be any help on the second part, because PyQt/PySide do things completely differently. The documentation you really want is this: [Unbound and Bound Signals](http://pyqt.sourceforge.net/Docs/PyQt4/new_style_signals_slots.html#unbound-and-bound-signals). – ekhumoro Mar 24 '15 at 22:52
  • 1
    Sure, the implementations of the signal-slot mechanism for PyQt and PySide are not identical, but they do share some similarities. The relevant one here is that for both, the `__new__` method of the QObject class inspects a class's attributes, and creates a usable signal attribute for every signal it sees. This is the reason why you cannot emit using the class's signal for both bindings. In addition, both do use the `QMetaObject` infrastructure to manage signals/slots under the hood. – nullstellensatz Mar 24 '15 at 23:48
  • 1
    The OP asked why there is a difference between invoking a signal as a class attribute rather than as an instance attribute. The difference is entirely to do with the syntactic sugar PyQt4 and PySide use to define custom signals. There is nothing equivalent to that in Qt. The relevant point is that the class attribute is not the same object as the instance attribute - they do not even have the same type. The `Worker.finished.emit(42)` line fails because unbound signals do not have an `emit` method. – ekhumoro Mar 25 '15 at 00:41