Q:

How to connect functions to PyQt signals outside of main thread

I'm creating a PyQt app where I want to have a background thread that connects some event handlers and then loops forever until the main window is closed. The problem I am experiencing is that the event handlers I am connecting only work if they are functions defined inside my MainWindow class. I have created a minimal repro below:
import threading
from PyQt5.QtWidgets import QApplication, QDialog, QPushButton, QVBoxLayout

class MainWindow(QDialog):
    def __init__(self):
        super(MainWindow, self).__init__()

        self.button1 = QPushButton("Click Me", self)
        self.button2 = QPushButton("Me Too!", self)

        layout = QVBoxLayout()
        layout.addWidget(self.button1)
        layout.addWidget(self.button2)
        self.setLayout(layout)

    def test(self):
        print("test inside class")

def test2():
    print("test outside class")

def main(window):
    window.button1.clicked.connect(window.test)
    window.button2.clicked.connect(test2)
    # Loop that runs in thread...

app = QApplication([])
window = MainWindow()
window.show()
threading.Thread(target=main, args=[window]).start()
app.exec_()

When I run this code, the first button prints a message to the console as expected, but the second one does nothing when clicked. If I run the main(window) function in the main thread, then both buttons work. I'm aware that in my small sample program this would be the obvious solution, but for reasons that are complex to explain I need to be able to connect the event handlers from the background thread in my application. Why does connecting a function like test2() that is defined outside the MainWindow class not work when I do it outside of the main thread?

A:

I'm still finding out the reason for the problem but the solution is to indicate the type of connection, in this case Qt::DirectConnection that will make the function test2 run on the same thread of the object that emits the signal (the object that emits the signal is the button that lives in the main thread).
import threading
from PyQt5 import QtCore, QtWidgets

class MainWindow(QtWidgets.QDialog):
    def __init__(self):
        super(MainWindow, self).__init__()
        self.button1 = QtWidgets.QPushButton("Click Me")
        self.button2 = QtWidgets.QPushButton("Me Too!")
        layout = QtWidgets.QVBoxLayout(self)
        layout.addWidget(self.button1)
        layout.addWidget(self.button2)

    @QtCore.pyqtSlot()
    def test(self):
        print("test inside class")

def test2():
    print("test outside class")

def main(window):
    window.button1.clicked.connect(window.test)
    window.button2.clicked.connect(test2, QtCore.Qt.DirectConnection)
    while True:
        QtCore.QThread.sleep(1)

if __name__ == '__main__':
    import sys
    app = QtWidgets.QApplication(sys.argv)
    window = MainWindow()
    window.show()
    threading.Thread(target=main, args=(window,), daemon=True).start()
    sys.exit(app.exec_())

