views:

965

answers:

4

I'm practicing PyQt and (Q)threads by making a simple Twitter client. I have two Qthreads.

  1. Main/GUI thread.

  2. Twitter fetch thread - fetches data from Twitter every X minutes.

So, every X minutes my Twitter thread downloads a new set of status updates (a Python list). I want to hand this list over to the Main/GUI thread, so that it can update the window with these statuses.

I'm assuming that I should be using the signal / slot system to transfer the "statuses" Python list from the Twitter thread, to the Main/GUI thread. So, my question is twofold:

  1. How do I send the statuses from the Twitter thread?

  2. How do I receive them in the Main/GUI thread?

As far as I can tell, PyQt can by default only send PyQt-objects via signals / slots. I think I'm supposed to somehow register a custom signal which I can then send, but the documentation on this that I've found is very unclear to a newbie like me. I have a PyQt book on order, but it won't arrive in another week, and I don't want to wait until then. :-)

I'm using PyQt 4.6-1 on Ubuntu

Update:

This is an excert from the code that doesn't work. First, I try to "connect" the signal ("newStatuses", a name I just made up) to the function self.update_tweet_list in the Main/GUI thread:

QtCore.QObject.connect(self.twit_in,
                       QtCore.SIGNAL("newStatuses (statuses)"),
                       self.update_tweet_list)

Then, in the Twitter thread, I do this:

self.emit(SIGNAL("newStatuses (statuses)"), statuses)

When this line is called, I get the following message:

QObject::connect: Cannot queue arguments of type 'statuses'
(Make sure 'statuses' is registered using qRegisterMetaType().)

I did a search for qRegisterMetaType() but I didn't find anything relating to Python that I could understand.

+2  A: 

(Py)Qt signals and slots work cross-threads just the same as in a single thread. So there's nothing really special to set up:

Define a slot (method) in the main thread, and connect the thread's signal to this slot (the connection shall be also done in the main thread). Then, in the thread, when you want to, just emit the signal and it should work. Here's a tutorial on using signals and slots with threading in PyQt.

I recommend you to try it on a small toy example first, before moving to your application.

Eli Bendersky
Thanks. I've followed that tutorial, but it didn't quite work for me. It seemed like the problem was that I was sending a non-PyQt object (my "statuses" Python list), while that tutorial sends QRect and QImage, which I assume are PyQt objects. When I tried sending my statuses list, I received a message saying I should "register" my signal (or somesuch). I'm usure how to do that, and I didn't quite understand the documentation I found on it, so that's why I came here.
Enfors
@Enfors: What do you mean by "sending a Python list"? as an argument to the signal? Can you show a minimal code sample that doesn't work for you?
Eli Bendersky
Yes, by "sending a Python list" I mean supply it as an argument to the signal. I've updated my question with some code and the exact error message at the end.
Enfors
+3  A: 

Check out this question I asked a while back. There is a code example that might help you figure out what you need to do.

What you said about registering your signal makes me think of this code (from the aforementioned question):

class ProcessingThread(threading.Thread, QtCore.QObject):
    __pyqtSignals__ = ( "progressUpdated(str)",
                        "resultsReady(str)")

I'm passing strings in my example, but you should be able to replace str with list.

If it turns out that you can't pass mutable objects, you can handle your results the way I do in my example (i.e. set a results variable in the thread, tell the main thread that they are ready, and have the main thread "pick them up").

Update:

You get the message QObject::connect: Cannot queue arguments of type 'statuses' because you need to define the type of argument that you will pass when you emit your signal. The type you want to pass is list not statuses.

When you connect your signal it should look like this:

QtCore.QObject.connect(self.twit_in,
                       QtCore.SIGNAL("newStatuses(list)"),
                       self.update_tweet_list)

When you emit your signal it should look like this:

self.emit(SIGNAL("newStatuses(list)"), statuses)

given that statuses is a list. Note that you may want to emit a deep copy of your list depending on your situation.

Update 2:

Ok, using list as the type is not correct. From the PyQt4 help reference:

PyQt Signals and Qt Signals

Qt signals are statically defined as part of a C++ class. They are referenced using the QtCore.SIGNAL() function. This method takes a single string argument that is the name of the signal and its C++ signature. For example::

QtCore.SIGNAL("finished(int)")

The returned value is normally passed to the QtCore.QObject.connect() method.

PyQt allows new signals to be defined dynamically. The act of emitting a PyQt signal implicitly defines it. PyQt v4 signals are also referenced using the QtCore.SIGNAL() function.

The PyQt_PyObject Signal Argument Type

It is possible to pass any Python object as a signal argument by specifying PyQt_PyObject as the type of the argument in the signature. For example::

QtCore.SIGNAL("finished(PyQt_PyObject)")

While this would normally be used for passing objects like lists and dictionaries as signal arguments, it can be used for any Python type. Its advantage when passing, for example, an integer is that the normal conversions from a Python object to a C++ integer and back again are not required.

The reference count of the object being passed is maintained automatically. There is no need for the emitter of a signal to keep a reference to the object after the call to QtCore.QObject.emit(), even if a connection is queued.

tgray
Thanks for your input. As it turns out, if I use "PyQt_PyObject" as the type (instead of "list"), then it works.
Enfors
Interesting. I use PyQt4 version 4.7 on Windows, so maybe there's difference. Anyway, I'm glad you found an answer!
tgray
I checked and I was wrong about using `list`. I'm going to update my answer so I don't mislead future readers.
tgray
+3  A: 

From this example:

http://doc.trolltech.com/4.5/qmetatype.html

 int id = QMetaType.type("MyClass");

You can write down in Python

from PyQt4 import QtCore    
id = QtCore.QMetaType.type('MyClass')

Edit

The answer extracted from the comment:

self.emit(SIGNAL("newStatuses(PyQt_PyObject)"), statuses)
Xavier
So, if I interpret this correctly, I can't send my Python list by itself (because a Python list isn't a class). Instead, I should make my own class which *contains* my Python list, correct?
Enfors
Yeah, but searching on google lead me to this: http://blog.qgis.org/node/102 Based on this article you could write: self.emit(SIGNAL("newStatuses(PyQt_PyObject)"), statuses)I didn't try it, so maybe give it a try ...
Xavier
Ah! That worked! Thanks! In fact, the PyQt_PyObject line was all that was needed, and then I *could* send my Python list "statuses" as an argument to the signal. QMetaType.type() isn't needed in my case, apparently...
Enfors
+2  A: 

You can also do this, which is much more pythonic (and readable!).

# create a signal equivalent to "void someSignal(int, QWidget)"
someSignal = QtCore.pyqtSignal(int, QtGui.QWidget)

# define a slot with the same signature
@QtCore.pyqtSlot(int, QtGui.QWidget)
def someSlot(status, source):
    pass

# connect the signal to the slot
self.someSignal.connect(self.someSlot)
markv