1

I have a Python PyQt application that displays a simple UI. When the user clicks a button in the UI it triggers a QThread. The use of a thread prevents the UI from "freezing" while the thread runs. I emit signals to pass information from the run thread back to the UI for status updates and to indicate completion. Everything works fine as described and I've created a simple class for my UI to call which creates the thread and runs my generic processing.

However, I would also like to create a command line version (No GUI) of my program and use the same processing QThread class. However, I get the following error when I try to connect my signals. It would seem that QThread was only meant for GUI programs?

AttributeError: MyClass instance has no attribute 'connect'

Is it possible to use QThread with a non-GUI program?

from PyQt4 import QtCore
from PyQt4.QtCore import * 

#======================================

class MyProcess(QThread):

    def __init__(self):
        QThread.__init__(self)

    def __del__(self):
        self.quit()
        self.wait()  

    def run(self):
        print "do time intensive process here"  
        self.emit( SIGNAL('processdone'), "emitting signal processdone") 
        return       

#====================================== 

class MyClass(QObject):

    def __init__(self, parent=None):            # All QObjects receive a parent argument (default to None)
        super(MyClass, self).__init__(parent)   # Call parent initializer.

        thread1 = MyProcess()  # uses QThread and emits signal 'processdone' 
        self.connect( thread1, SIGNAL("processdone"), self.thread1done)    
        thread1.start()  

    def thread1done(self):
        print "done"      

#======================================

if __name__ == "__main__": 

    MyClass()
2
  • This sounds like a situation where the right approach is to reactor your code. Instead of extending QThread (or Thread in python's standard library) have a class that does the work and can be instantiated with both Commented Aug 19, 2014 at 20:43
  • can you show whats the difference between gui and nongui versions in your code? Commented Aug 19, 2014 at 20:44

1 Answer 1

7

The problem isn't QThread, the problem is that you're calling the connect method from a class which doesn't have it. You need to make MyClass inherits from QObject.

In the GUI this works because whatever widget you're working with (QDialog, QMainWindow, QWidget ...) it inherits (directly or indirectly) from QObject.

To make MyClass inherit from QObject you only have to:

class MyClass(QObject):                         # Specify the class your are specializing.
    def __init__(self, parent=None):            # All QObjects receive a parent argument (default to None)
        super(MyClass, self).__init__(parent)   # Call parent initializer.

        # And countinue your code here... 

I also recommend you to use the New-style Signal and Slot Support.

Everything works except the processdone signal is called but apparently it never triggers call to thread1done.

The problem I can spot is you don't have defined a processdone signal. That signal does not exist for Qt. Check the link I left you to learn about custom signals. Meanwhile you can add:

class MyProcess(QThread):
    processdone = QtCore.pyqtSignal("QString")

at the beginning of the class.

And one last thing, but very important. You aren't working with GUI, but you are still using QObjects and the Qt singal mechanisms, which depends on the main Qt loop to work. Hence, you still need a QApplication object despite of your application is a non-gui program.

Here is your code, now working:

from PyQt4 import QtCore
from PyQt4 import QtGui
from PyQt4.QtCore import * 

class MyProcess(QThread):
    processdone = QtCore.pyqtSignal("QString") # Define custom signal.
    def __init__(self, parent = None):
        QThread.__init__(self, parent)
    def run(self):
        print("do time intensive process here")
        self.emit( SIGNAL('processdone'), "emitting signal processdone")
        return       

class MyClass(QObject):

    def __init__(self, parent=None):            # All QObjects receive a parent argument (default to None)
        super(MyClass, self).__init__(parent)   # Call parent initializer.

        thread1 = MyProcess(self) 
        self.connect( thread1, SIGNAL("processdone"), self.thread1done)    
        thread1.start()  

    @QtCore.pyqtSlot("QString")         # Tell Python this is a QTSLOT an receives a string
    def thread1done(self, text):
        print(text)                     # Print the text from the signal.

if __name__ == "__main__":
    import sys
    app = QtGui.QApplication(sys.argv)  # You still need a QApplication object.
    a = MyClass()
    sys.exit(app.exec())
Sign up to request clarification or add additional context in comments.

6 Comments

I knew it would be something simple like this... I will try it tomorrow and let you know how it goes, but I expect this answer will get the check. :)
I've added code above. What changes should I make to inherit from QObject? My GUI version works because (as you said) I inherit QMainWindow, but I can't use QMainWindow in the non-GUI logic.
You aren't a pain, but you have to be fair. The original cuestion (about QThreads) and the other question about how inherits have been answered. Don't think if you check the answer I'll stop helping you, it is the opposite. About you last comment, probably something went wrong within the __init__ method when python defining the class.It's really a bit hard to know because I haven't your code, hace I can't reproduce the error.
I figured out my syntax mistake for the previous comment. I've updated the full working code above. Everything works except the processdone signal is called but apparently never triggers call to thread1done?
Have you readed my recommendations on the New-style Signal and Slot Support? The link is at the end of the answer. The problem I can spot is you don't have defined a processdone signal. That signal does not exist for Qt. Check the link I left you to learn about custom signals.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.