PyQt5 + Python 3: passing lists, dicts as signal a

2019-06-23 21:35发布

问题:

I am using pyqtSignal to send a python list as an argument from worker thread to main thread. When does qt create a copy of the object being passed as argument. According to: http://www.embeddeduse.com/2013/06/29/copied-or-not-copied-arguments-signals-slots/ the qt should make a copy of the object. However, in the example below the main thread can change the contents of the list being sent from another thread.

import sys
import time
from PyQt5.QtCore import QThread, QObject, pyqtSlot, pyqtSignal
from PyQt5.QtWidgets import QApplication

class ClassProcessing(QObject):

    py_sig_send_data = pyqtSignal(list)

    def __init__(self):
        super().__init__()
        # initialize some variables
        self.data = [1, 2, 3, 4, 5]

    def worker(self):
        print(self.data)
        self.py_sig_send_data.emit(self.data)
        time.sleep(1)
        print("modfied data in thread", self.data)

class ClassProcessingThread(QObject):
    def __init__(self):
        super().__init__()
        self.objThread = QThread()
        self.objThread_id = 1
        self.objThread_finished = False
        self.processing = ClassProcessing()
        self.processing.moveToThread(self.objThread)
        self.objThread.started.connect(self.processing.worker)
        self.objThread.start()

class SomeClass(QObject):
    def __init__(self):
        super().__init__()

    @pyqtSlot(list)
    def receive_data(self, data):
        print("received data", data)
        data[1] = 42
        print("modified data", data)

def main():
    app = QApplication(sys.argv)
    processing_thread = ClassProcessingThread()
    some_class = SomeClass()
    processing_thread.processing.py_sig_send_data.
        connect(some_class.receive_data)
    sys.exit(app.exec_())

if __name__ == '__main__':
    main()

The output:

[1, 2, 3, 4, 5]
received data [1, 2, 3, 4, 5]
modified data [1, 42, 3, 4, 5]
modified data in thread [1, 42, 3, 4, 5]

Can someone please explain to me how to pass a list in a pyqtSignal in a thread-safe manner. Thanks.

回答1:

PyQt doesn't behave in the same way as Qt when it comes to passing container types between threads using signals.

Specifically, there is no automatic conversion [1] to the equivalent Qt types, and therefore no implicit copying. PyQt does provide a mechanism to explicitly request such conversions, though. To do that, you can define a custom signal using either QVariantList or QVariantMap:

    py_sig_send_data = pyqtSignal('QVariantList')

However, it's important to note that QVariantMap only supports string keys.

All in all, though, it's probably simpler, clearer and safer to just explictly copy mutable python types before passing them via signals across threads.

[1] Or at least, not any more.