Target a function with QThread - python

I'm aware of this structure
class MyThread(QThread):
def __init__(self):
super().__init__()
def run():
# do stuff
t = MyThread()
t.start()
With regular threading.Thread you can do something like this:
def stuff():
# do stuff
t = threading.Thread(target=stuff)
t.start()
Any way to do this in pyqt5 with QThreads? Something like this:
t = Qthread(target=stuff)
t.start()
I tried that but I got this error:
TypeError: 'target' is an unknown keyword argument

You can add the function to a custom argument in the __init__, create an instance attribute for its reference and then run it in the run.
class MyThread(QThread):
def __init__(self, target=None):
super().__init__()
self.target = target
def run():
if self.target:
self.target()
def stuff():
# do something
t = MyThread(target=stuff)
t.start()
Be aware that access to UI elements is not allowed in external threads, so don't use the threaded function to do anything related to UI: reading values and properties is unreliable, and writing can cause your program to crash.

Related

Passing Generic type to inner class

In this code, I want to pass the type T to the inner class Emitter.
T = TypeVar('T')
class MySignal(Generic[T]):
class Emitter(QtCore.QObject,Generic[T]):
signal = Signal(T)
def __init__(self):
super(MySignal.Emitter, self).__init__()
def __init__(self):
self.emitter = MySignal.Emitter[T]()
def emit(self,*args,**kw):
self.emitter.signal.emit(*args,**kw)
It doesn't behave as expected.
If I do
minMaxChanged=MySignal[tuple]()
Then minMaxChanged.emitter.__orig_class__.__args__[0] looks like T~ instead of tuple. The minMaxChanged class itself is as expected.
Generally, specifying Signal(typ) results in typ being the argument type expected to be passed to emit function. Consequently, in this case, Signal is called as if it were called without args, and calling emit with 1 argument fails since it expects no args.
I also expected the code
T = TypeVar('T')
class MySignal(Generic[T]):
class Emitter(QtCore.QObject):
signal = Signal(T)
def __init__(self):
super(MySignal.Emitter, self).__init__()
def __init__(self):
self.emitter = MySignal.Emitter()
def emit(self,*args,**kw):
self.emitter.signal.emit(*args,**kw)
to work. However, the exact same issue remains.
Update
Notice that this is not a redundant complication. This class (partially brought here) is meant to wrap a Qt Signal which can only be omitted from class that is a Qt class. It meant to provide a generic signal wrapper. See how to emit signal from a non PyQt class?
Trying double inheritance MySignal(QtCore.QObject, Generic[T]) resulted in a c++ style crash.
#ekhumu suggestion indeed works. I hoped for something prettier.
class MySignal:
def __init__(self,typ):
Emitter = type('Emitter', (QtCore.QObject,), {'signal': Signal(typ)})
self.emitter = Emitter()
def emit(self,*args,**kw):
self.emitter.signal.emit(*args,**kw)
def connect(self, slot):
self.emitter.signal.connect(slot)

How to access instance variables from a threaded instance method?

I have been struggling to figure out how to access the the instance variable from an instance method running in a separate thread like in the example below. I thought the loop would be skipped after the test.set_finished method is called from the main thread after 5 seconds.
However my loop keeps counting and I fail to understand why. Would be nice if someone could help me out a little and tell me what I need to do differently or what I'm trying to do is not possible at all.
import time
import concurrent.futures
class TestClass(object):
def __init__(self):
super(TestClass, self).__init__()
self.finished = False
def do_something(self):
i = 0
while not self.finished:
i+=1
print(i)
time.sleep(1)
def set_finished(self, finished_arg):
self.finished = finished_arg
def startThreadedMethod(self):
with concurrent.futures.ThreadPoolExecutor() as executor:
t1 = executor.submit(self.do_something)
test = TestClass()
test.startThreadedMethod()
time.sleep(5)
test.set_finished(True)

QTimer not executed when called inside Singleton/Borg

I've implemented a timer using QTimer inside a Singleton. The Singleton is implemented using the Borg pattern. If I start a QTimer with single shot inside a function of the Singleton it won't be executed. The same call in a function outside the Singleton works well.
This is the code:
#!/usr/bin/env python
import sys
from PyQt5.QtCore import QTimer
from PyQt5.QtWidgets import QApplication
class Borg():
_shared_state = {}
def __init__(self):
self.__dict__ = self._shared_state
class Timers(Borg):
def __init__(self):
Borg.__init__(self)
def update_not_working(self):
QTimer().singleShot(2000, Timers().update_not_working)
print('update not working')
def update_working():
QTimer().singleShot(2000, update_working)
print('update working')
if __name__ == '__main__':
app = QApplication(sys.argv)
print('start timer')
Timers().update_not_working()
update_working()
sys.exit(app.exec_())
The output is (no error, no exception):
start timer
update not working
update working
update working
....
Why is one call working and the other not? Is there something wrong with my implementation of the Borg or with the usage of QTimer?
print self in update_not_working and print Timers() in update working show that the Timers object before the event loop started is different from the one within:
update not working
<__main__.Timers instance at 0xb52162cc>
update working
<__main__.Timers instance at 0xb52162cc>
update working
<__main__.Timers instance at 0xb521650c>
update working
<__main__.Timers instance at 0xb521650c>
update working
<__main__.Timers instance at 0xb521650c>
update working
<__main__.Timers instance at 0xb521650c>
#classmethod should help here because it allows to call the method on an instance OR on the class as you do in the single shot statement.
Compare: When should I use #classmethod and when def method(self)?
This is actually just a matter of normal garbage-collection.
If you add some debugging code to your example like this:
class Timers(Borg):
def __init__(self):
Borg.__init__(self)
print('init:', self)
def update_not_working(self):
QTimer().singleShot(1, Timers().update_not_working)
print('update not working')
def __del__(self):
print('deleted:', self)
it will produce output like this:
start timer
init: <__main__.Timers object at 0x7f194bf53eb8>
init: <__main__.Timers object at 0x7f1940cfdb00>
deleted: <__main__.Timers object at 0x7f1940cfdb00>
update not working
deleted: <__main__.Timers object at 0x7f194bf53eb8>
update working
update working
As you can see, both of the Timers instances get deleted long before the single-shot timer sends its timeout() signal. And when they deleted, their instance-methods get deleted as well, which will automatically disconnect them from the signal. This shows that the Borg pattern does not produce a true singleton: it just mimics some of the behaviour of one.
If you use a real singleton class, like this:
class Timers2(object):
_instance = None
def __new__(cls):
if Timers2._instance is None:
Timers2._instance = object.__new__(cls)
return Timers2._instance
def update_not_working(self):
QTimer().singleShot(2000, Timers2().update_not_working)
print('update not working')
your example will work as expected. This is because there is only ever one instance, and it is kept alive by being cached as a class attribute.
Finally, the reason why the update_working() succeeds, is because it is a globally defined function. As with the class attribute, this ensures that it won't get garbage-collected until the script completes.
class Borg():
_shared_state = {}
def __init__(self):
self.__dict__ = self._shared_state
class Timers(Borg):
def __init__(self):
Borg.__init__(self)
#classmethod
def update_not_working(cls):
QTimer().singleShot(2000, Timers().update_not_working)
print('update not working')
def update_working():
QTimer().singleShot(2000, update_working)
print('update working')
if __name__ == '__main__':
app = QApplication(sys.argv)
print('start timer')
Timers().update_not_working()
update_working()
sys.exit(app.exec_())

Superclass __init__ not recognizing its kwargs

I'm trying to use the StoppableThread class presented as an answer to another question:
import threading
# Technique for creating a thread that can be stopped safely
# Posted by Bluebird75 on StackOverflow
class StoppableThread(threading.Thread):
"""Thread class with a stop() method. The thread itself has to check
regularly for the stopped() condition."""
def __init__(self):
super(StoppableThread, self).__init__()
self._stop = threading.Event()
def stop(self):
self._stop.set()
def stopped(self):
return self._stop.isSet()
However, if I run something like:
st = StoppableThread(target=func)
I get:
TypeError: __init__() got an unexpected keyword argument 'target'
Probably an oversight on how this should be used.
The StoppableThread class does not take or pass any additional arguments to threading.Thread in the constructor. You need to do something like this instead:
class StoppableThread(threading.Thread):
"""Thread class with a stop() method. The thread itself has to check
regularly for the stopped() condition."""
def __init__(self,*args,**kwargs):
super(threading.Thread,self).__init__(*args,**kwargs)
self._stop = threading.Event()
This will pass both positional and keyword arguments to the base class.
You are overriding init and your init doesn't take any arguments. You should add a "target" argument and pass it through to your base class constructor with super or even better allow arbitrary arguments via *args and *kwargs.
I.e.
def __init__(self,*args,**kwargs):
super(threading.Thread,self).__init__(*args,**kwargs)
self._stop = threading.Event()

How do I tell a class method to wait until a signal from a QDialog class method is caught?

I have the following code:
class Functions(QObject):
mysig = Signal(filename)
def __init__(self, parent=None):
super(Functions, self).__init__(parent)
self.result = None
def showDialog(self, filename):
self.mysig.emit(filename)
def grabResult(self):
while not self.result:
time.sleep(5)
return result #this is the question
def setResult(self, result):
self.result = result
The other part of the code has this:
class Dialog(QDialog):
anotherSig = Signal(str)
fun = Functions()
def __init__(self, parent=None, filename=filename):
self.filename = filename
#Here it displays a picture based on the filename parameter
def okButtonClicked(self):
text = self.lineedit.text()
fun.setResult(text)
#Tried also this:
self.anotherSig.emit(text)
The Functions() class is called from a worker QThread (not shown here).
I guess my question is this: how do I tell my Functions class that the user has entered the the text and clicked the OK button? I tried connecting that anotherSig Signal, but when I try to do so, Qt complains about QPixmaps not being safe to be set from a different thread, and it doesn't work.
The method that I am using here "works", but I feel it's not very reliable. Plus, it only works when all of the relevant methods in the Functions class are #classmethod - this way, for some reason, it doesn't work. The setResult is called (I added a print statement to make sure), but the grabResult still shows self.result as None.
This code is not working because the call to showDialog is happening on the instantiation of a Functions object that is an attribute of what ever object is off on the other thread. Your fun in Dialog, which you set the result on, is a different instantiation.
To move the results back to the original Functions object I think you need to connect anotherSig of the Dialog object to the setResult function on the Functions object you want to get the results back.
Does something like this work (hard to test this with out a good bit of boiler plate).
class Functions(QObject):
mysig = Signal(filename,Functions)
def __init__(self, parent=None):
super(Functions, self).__init__(parent)
self.result = None
def showDialog(self, filename):
self.mysig.emit(filename,self)
def grabResult(self):
while not self.result:
time.sleep(5)
return result #this is the question
#QtCore.Slot(str)
def setResult(self, result):
self.result = result
def connection_fun(filename,fun):
d = Dialog(filename)
# what ever else you do in here
d.anotherSig.connect(fun.setResult))
Using time.sleep causes your application to freeze. One method for making your class wait is using QEventLoop like this:
loop = QEventLoop()
myDialog.mySignal.connect(loop.quit)
loop.exec_()

Categories