How to implement events in Python? - python-3.x

Can't figure out how to implement event system.
I am doing project with tkinter.
And I need to use events.
How to have events like Java or C# events?
I searched a lot but can't figure out the right way.
Here is the event class I am trying to implement.
class Event(object):
def __init__(self):
self.handlers = []
def add(self, handler):
self.handlers.append(handler)
return self
def remove(self, handler):
self.handlers.remove(handler)
return self
def fire(self, sender, earg=None):
for handler in self.handlers:
value = handler()
self.remove(handler)
return value
__iadd__ = add
__isub__ = remove
__call__ = fire
Here is Car class
class Car:
_speed = 0
events = Event()
def speed_up(self):
self._speed += 10
def speed_down(self):
self._speed -= 10
def get_speed(self):
return self._speed
And for last there is Window class (tkinter window)
class Window(tk.Tk):
def __init__(self):
super().__init__()
self.car = Car()
tk.Button(self, text="Speed Up", command=self.increase_speed).grid(sticky="nsew")
tk.Button(self, text="Speed Down", command=self.decrease_speed).grid(sticky="nsew")
self.speed_label = tk.Label(self, text="0")
self.speed_label.grid(sticky="nsew")
self.mainloop()
def increase_speed(self):
self.car
def decrease_speed(self):
pass
Here is tkinter window:
I want to accomplish:
1) On Speed Up button click "speed_up" should be added to events.
2) It should change value of self.speed_label.
3) It should be something like c# / Java events or c# delagetes.
Trying to learn this new concept to me. But having difficult time implementing this...
UPDATED!
I was searching / editing and came up with a solution.
Don't know if this solution is good.
I will ask my teacher if this is a good way for implementing events.
But for now the code look like this:
import tkinter as tk
class Observer():
_observers = []
def __init__(self):
self._observers.append(self)
self._observed_events = []
def observe(self, event_name, callback_fn):
self._observed_events.append({'event_name' : event_name, 'callback_fn' : callback_fn})
class Event():
def send(self, event_name, *callback_args):
for observer in Observer._observers:
for observable in observer._observed_events:
if observable['event_name'] == event_name:
observable['callback_fn'](*callback_args)
def receive(self, event_name, *callback_args):
for observer in Observer._observers:
for observable in observer._observed_events:
if observable['event_name'] == event_name:
response = observable['callback_fn'](*callback_args)
return response
class Car(Observer):
def __init__(self):
Observer.__init__(self)
self._current_speed = 0
def speed(self):
self._current_speed += 10
def slow(self):
self._current_speed -= 10
def current(self):
return self._current_speed
class Window(tk.Tk):
def __init__(self):
super().__init__()
self._car = Car()
self.store()
self.events = Event()
tk.Button(self, text="Speed Up", command=lambda:self.change_speed("speed")).grid(sticky="nsew")
tk.Button(self, text="Slow Down", command=lambda:self.change_speed("slow")).grid(sticky="nsew")
self.label = tk.Label(self, text=0)
self.label.grid()
self.settings()
def store(self):
self._car.observe("speed", self._car.speed)
self._car.observe("slow", self._car.slow)
self._car.observe("current", self._car.current)
def settings(self):
self.mainloop()
def change_speed(self, event):
self.events.send(event)
self.label.config(text=self.events.receive("current"))
Window()

Without fully analyzing the code in the question, I'd say you're in the right direction by using function callbacks. That's because Python, as far as I know, does not have a native implementation of events.
Some useful libraries or examples that build on this can be seen in articles such as the observer pattern, mimicking events or in answers to a related question.
This is the simplest code I can think of that illustrates the callback concept, without arguments:
def on_notify():
print("OK, I'm up-to-date")
def do_something(update):
# do whatever I need to do
print("I've changed\n")
update()
do_something(on_notify)
Which outputs:
I've changed
OK, I'm up-to-date
Our "worker" function takes a function parameter to be called when an event happens. In this case it's only one, but a list could be used, so that we have many watchers, which is what the other more complete examples do.
Also relevant are event objects, a mechanism for objects to communicate between threads, which is something worth considering for user interfaces. My guess is that most libraries and frameworks out there which implement the "missing events" functionality are based on either or both of these core methods.

You can find a good explanation on how to implement observers and events in a language that doesn't have them by default in here, it is written in c++, but the implementation is almost the same in python.
I would probably have used a list of observers for each event, so instead of the event having to search for its observers in the observers' list it just has to cycle through its personal list of observers to notify them. If the event has specific parameters that are important like the id of the entity who activated it you could include it on the notification, each observer decides what to do with that, they can simply do nothing if that information isn't relevant to them.

Related

Is there way to write code usable for both of the multi-processing and single-process in python?

The problem is the lock. The multiprocessing needs a lock but not for the single process. For example, consider the following code:
Class Test():
def __init__(self, rlock = None):
self.tlock = rlock
def do_test(self, invalue):
with self.tlock:
return invalue + 1
For the multiprocessing, I need to use the tlock but when I use the class for single process, I don't need it. So the line with self.tlock doesn't make sense for a single process execution.
My immediate thought is to write in the following way:
def do_test(self, invalue):
if self.tlock is not None:
with self.tlock:
return invalue + 1
else:
return invalue + 1
But this looks so awkward as I would have handful of methods of this pattern inside the class.
Is there any elegant and efficient way to write the code for the code reuse?
You can create a dummy class that can support context managers, and use that instead of storing the lock if it is None (i.e, no multiprocessing is involved):
class DummyLock:
def __enter__(self):
pass
def __exit__(self, exc_type, exc_val, exc_tb):
pass
class Test:
def __init__(self, rlock=None):
self.tlock = rlock
if self.tlock is None:
self.tlock = DummyLock()
All other methods in the class will not need to be changed unless they are accessing self.rlock specific methods (like self.rlock.acquire()) instead of using context managers (with self.rlock:).

PySide6 QThread still freezing main GUI

I am currently trying to implement some threading functionality in my PySide6 GUI application. I followed a tutorial to try to get started (link is here), and I cannot seem to get it to work. Although that tutorial uses PyQt not PySide, the classes and structure is still similar, and it does seem to launch on another thread. Still though, it freezes the main GUI, which is not desired when this actually faces users.
Here is a sample of my code:
class Worker(QObject):
finished = Signal(str)
progress = Signal(int)
def run(self, file):
"""Long-running task." that calls a separate class for computation""
b = SeparateClass()
b.doComputation()
self.finished.emit()
class DataPlotting(QMainWindow):
def __init__(self):
self.thread = QThread()
self.worker = Worker()
self.report_builder = QPushButton('Call class that threads')
self.report_builder.setEnabled(False)
self.report_builder.clicked.connect(self.qthread_test)
def qthread_test(self):
file = 'some_file.txt'
self.worker.moveToThread(self.thread)
self.thread.started.connect(self.worker.run(file))
self.worker.finished.connect(self.thread.quit)
self.worker.finished.connect(self.worker.deleteLater)
self.thread.finished.connect(self.thread.deleteLater)
self.thread.start()
return
This does accomplish the work that is in the Worker class and spit out the desired results, but it freezes the GUI. I am not really sure what I am doing wrong, as this approach is what has been suggested to prevent freezing GUIs for heavy computation.
Is there something that I am straight up missing? Or am I going about this the wrong way? Any help or guidance is appreciated
I am assuming that you make the appropriate calls to the super class during __init__ for your subclasses of QMainWindow and the QObject.
When your code executes self.thread.started.connect(self.worker.run(file)) that line it runs the function self.worker.run(file) immediately and assigns the result of that function, which is None, as the connected slot to the thread.started signal. Instead of passing the file path as a parameter you can assign it to the worker instance and have the run method grab the path from self during execution.
For example you can try something like this:
class Worker(QObject):
finished = Signal(str)
progress = Signal(int)
def run(self):
"""Long-running task." that calls a separate class for computation"""
file = self.some_file
b = SeparateClass()
b.doComputation()
self.finished.emit()
class DataPlotting(QMainWindow):
def __init__(self):
self.report_builder = QPushButton('Call class that threads')
self.report_builder.setEnabled(False)
self.report_builder.clicked.connect(self.qthread_test)
self.threads = []
def qthread_test(self):
worker = Worker()
thread = QThread()
worker.some_file = 'some_file.txt'
worker.moveToThread(thread)
thread.started.connect(worker.run)
worker.finished.connect(thread.quit)
worker.finished.connect(worker.deleteLater)
thread.finished.connect(thread.deleteLater)
thread.start()
self.threads.append(thread)
return

QGraphicsEffect on pyqt, blinking a button

I am building a GUI on python and pyqt.
The GUI has a lot of pushbuttons, generated through class LED, meaning each led has 3 buttons, for an n number of leds.
In a few of the buttons, I want an effect that changes the opacity of the pushbutton, in a loop from 0 to 1 and back again, so it disappears and appears. I need only one process to manage all, so the effect starts at same time for every button and all blink at the same time.
I've managed to achieve that, through qgraphicseffect in a thread, iterating through a list.
The problem is that after a few minutes, the effect stops, although the thread is still running (print(opacity_level)). more pushbuttons with the effect makes even shorter duration. Clicking any button, even others without effect, restarts the gui animation.
My small research in threading on pyqt made me implement this thread manager, although I do not fully understand it.
class WorkerSignals(QtCore.QObject):
finished = QtCore.pyqtSignal()
error = QtCore.pyqtSignal(tuple)
result = QtCore.pyqtSignal(object)
progress = QtCore.pyqtSignal(tuple)
class Worker(QtCore.QRunnable):
'''
Worker thread
Inherits from QRunnable to handler worker thread setup, signals and wrap-up.
'''
def __init__(self, fn, *args, **kwargs):
super(Worker, self).__init__()
# Store constructor arguments (re-used for processing)
self.fn = fn
self.args = args
self.kwargs = kwargs
self.signals = WorkerSignals()
# Add the callback to our kwargs
self.kwargs['progress_callback'] = self.signals.progress
#pyqtSlot()
def run(self):
'''
Initialise the runner function with passed args, kwargs.
'''
# Retrieve args/kwargs here; and fire processing using them
try:
result = self.fn(*self.args, **self.kwargs)
except:
traceback.print_exc()
exctype, value = sys.exc_info()[:2]
self.signals.error.emit((exctype, value, traceback.format_exc()))
else:
self.signals.result.emit(result) # Return the result of the processing
finally:
self.signals.finished.emit() # Done
Next the leds class
class LEDs:
def __init__(self,name,group,frame):
self.opacity_effect = QtWidgets.QGraphicsOpacityEffect()
self.button_auto = QtWidgets.QPushButton()
self.button_auto.setObjectName("button_auto_neutral")
self.button_auto.clicked.connect(lambda state, x=self: self.AutoMode())
def AutoMode(self):
print(self.name,"Automode")
if len(settings.blink) ==0: # start thread only if no previous thread, both thread and
this reference the size of settings.blink, so not ideal.
print("start thread")
settings.ledAutomode()
settings.blink.append(self)
And finally the settings class, which has the thread with the effect performing action. There is a second thread, which handles the icon of the button, accordingly with a timetable.
class Settings:
def __init__(self):
self.blink=[]
def ledAutomode(self):
def blink(progress_callback):
print("opacity")
op_up=[x/100 for x in range(0,101,5)]
op_down=op_up[::-1]; op_down=op_down[1:-1]; opacity=op_up+op_down
while len(self.blink) !=0:
for i in opacity:
print(i)
QtCore.QThread.msleep(80)
for led in self.blink:
led.opacity_effect.setOpacity(i)
def timeCheck(progress_callback):
while len(self.blink) != 0:
QtCore.QThread.msleep(500)
for led in self.blink:
matrix = [v for v in settings.leds_config[led.group][led.name]["Timetable"]]
matrix_time=[]
...
# some code
...
if sum(led_on_time):
led.button_auto.setObjectName("button_auto_on")
led.button_auto.setStyleSheet(ex.stylesheet)
else:
led.button_auto.setObjectName("button_auto_off")
led.button_auto.setStyleSheet(ex.stylesheet)
QtCore.QThread.msleep(int(30000/len(self.blink)))
worker = Worker(blink) # Any other args, kwargs are passed to the run function
ex.threadpool.start(worker)
worker2 = Worker(timeCheck) # Any other args, kwargs are passed to the run function
ex.threadpool.start(worker2)
So, perhaps a limitation on qgraphicseffect, or some problem with the thread (although its keeps printing), or I made some error.
I've read about subclassing the qgraphicseffect but I don't know if that solves the problem.
If anyone has another implementation, always eager to learn.
Grateful for your time.
Widgets are not thread-safe.
They cannot be created nor accessed from external threads. While it "sometimes" works, doing it is wrong and usually leads to unexpected behavior, drawing artifacts and even fatal crash.
That said, you're making the whole process incredibly and unnecessarily convoluted, much more than it should be, most importantly because Qt already provides both timed events (QTimer) and animations.
class FadeButton(QtWidgets.QPushButton):
def __init__(self):
super().__init__()
self.effect = QtWidgets.QGraphicsOpacityEffect(opacity=1.0)
self.setGraphicsEffect(self.effect)
self.animation = QtCore.QPropertyAnimation(self.effect, b'opacity')
self.animation.setStartValue(1.0)
self.animation.setEndValue(0.0)
self.animation.setDuration(1500)
self.animation.finished.connect(self.checkAnimation)
self.clicked.connect(self.startAnimation)
def startAnimation(self):
self.animation.stop()
self.animation.setDirection(self.animation.Forward)
self.animation.start()
def checkAnimation(self):
if not self.animation.value():
self.animation.setDirection(self.animation.Backward)
self.animation.start()
else:
self.animation.setDirection(self.animation.Forward)
If you want to synchronize opacity amongst many widgets, there are various possibilities, but a QVariantAnimation that updates all opacities is probably the easier choice:
class LEDs(QtWidgets.QWidget):
def __init__(self, parent=None):
super().__init__(parent)
layout = QtWidgets.QHBoxLayout(self)
self.animation = QtCore.QVariantAnimation()
self.animation.setStartValue(1.0)
self.animation.setEndValue(0.0)
self.animation.setDuration(1500)
self.animation.valueChanged.connect(self.updateOpacity)
self.animation.finished.connect(self.checkAnimation)
self.buttons = []
for i in range(3):
button = QtWidgets.QPushButton()
self.buttons.append(button)
layout.addWidget(button)
effect = QtWidgets.QGraphicsOpacityEffect(opacity=1.0)
button.setGraphicsEffect(effect)
button.clicked.connect(self.startAnimation)
# ... as above ...
def updateOpacity(self, opacity):
for button in self.buttons:
button.graphicsEffect().setOpacity(opacity)
Note that you shouldn't change the object name of a widget during runtime, and doing it only because you want to update the stylesheet is wrong. You either use a different stylesheet, or you use the property selector:
QPushButton {
/* default state */
background: #ababab;
}
QPushButton[auto_on="true"] {
/* "on" state */
background: #dadada;
}
class FadeButton(QtWidgets.QPushButton):
def __init__(self):
super().__init__()
# ...
self.setProperty('auto_on', False)
def setAuto(self, state):
self.setProperty('auto_on', state)
self.setStyleSheet(self.styleSheet())

Python - How can I implement a 'stoppable' thread?

There is a solution posted here to create a stoppable thread. However, I am having some problems understanding how to implement this solution.
Using the code...
import threading
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_event = threading.Event()
def stop(self):
self._stop_event.set()
def stopped(self):
return self._stop_event.is_set()
How can I create a thread that runs a function that prints "Hello" to the terminal every 1 second. After 5 seconds I use the .stop() to stop the looping function/thread.
Again I am having troubles understanding how to implement this stopping solution, here is what I have so far.
import threading
import time
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_event = threading.Event()
def stop(self):
self._stop_event.set()
def stopped(self):
return self._stop_event.is_set()
def funct():
while not testthread.stopped():
time.sleep(1)
print("Hello")
testthread = StoppableThread()
testthread.start()
time.sleep(5)
testthread.stop()
Code above creates the thread testthread which can be stopped by the testthread.stop() command. From what I understand this is just creating an empty thread... Is there a way I can create a thread that runs funct() and the thread will end when I use .stop(). Basically I do not know how to implement the StoppableThread class to run the funct() function as a thread.
Example of a regular threaded function...
import threading
import time
def example():
x = 0
while x < 5:
time.sleep(1)
print("Hello")
x = x + 1
t = threading.Thread(target=example)
t.start()
t.join()
#example of a regular threaded function.
There are a couple of problems with how you are using the code in your original example. First of all, you are not passing any constructor arguments to the base constructor. This is a problem because, as you can see in the plain-Thread example, constructor arguments are often necessary. You should rewrite StoppableThread.__init__ as follows:
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self._stop_event = threading.Event()
Since you are using Python 3, you do not need to provide arguments to super. Now you can do
testthread = StoppableThread(target=funct)
This is still not an optimal solution, because funct uses an external variable, testthread to stop itself. While this is OK-ish for a tiny example like yours, using global variables like that normally causes a huge maintenance burden and you don't want to do it. A much better solution would be to extend the generic StoppableThread class for your particular task, so you can access self properly:
class MyTask(StoppableThread):
def run(self):
while not self.stopped():
time.sleep(1)
print("Hello")
testthread = MyTask()
testthread.start()
time.sleep(5)
testthread.stop()
If you absolutely do not want to extend StoppableThread, you can use the current_thread function in your task in preference to reading a global variable:
def funct():
while not current_thread().stopped():
time.sleep(1)
print("Hello")
testthread = StoppableThread(target=funct)
testthread.start()
sleep(5)
testthread.stop()
I found some implementation of a stoppable thread - and it does not rely that You check if it should continue to run inside the thread - it "injects" an exception into the wrapped function - that will work as long as You dont do something like :
while True:
try:
do something
except:
pass
definitely worth looking at !
see : https://github.com/kata198/func_timeout
maybe I will extend my wrapt_timeout_decorator with such kind of mechanism, which You can find here : https://github.com/bitranox/wrapt_timeout_decorator
Inspired by above solution I created a small library, ants, for this problem.
Example
from ants import worker
#worker
def do_stuff():
...
thread code
...
do_stuff.start()
...
do_stuff.stop()
In above example do_stuff will run in a separate thread being called in a while 1: loop
You can also have triggering events , e.g. in above replace do_stuff.start() with do_stuff.start(lambda: time.sleep(5)) and you will have it trigger every 5:th second
The library is very new and work is ongoing on GitHub https://github.com/fa1k3n/ants.git

PyQt: How do I handle QPixmaps from a QThread?

This has to be the biggest nuisance I've encountered with PyQT: I've hacked together a thumbnailing thread for my application (I have to thumbnail tons of big images), and it looks like it would work (and it almost does). My main problem is this error message whenever I send a SIGNAL from my thread:
QPixmap: It is not safe to use pixmaps outside the GUI thread
I can't figure out how to get around this. I've tried passing a QIcon through my SIGNAL, but that still generates the same error. If it helps, here's the code blocks which deal with this stuff:
The Thumbnailer class:
class Thumbnailer(QtCore.QThread):
def __init__(self, ListWidget, parent = None):
super(Thumbnailer, self).__init__(parent)
self.stopped = False
self.completed = False
self.widget = ListWidget
def initialize(self, queue):
self.stopped = False
self.completed = False
self.queue = queue
def stop(self):
self.stopped = True
def run(self):
self.process()
self.stop()
def process(self):
for i in range(self.widget.count()):
item = self.widget.item(i)
icon = QtGui.QIcon(str(item.text()))
pixmap = icon.pixmap(72, 72)
icon = QtGui.QIcon(pixmap)
item.setIcon(icon)
The part which calls the thread (it occurs when a set of images is dropped onto the list box):
self.thread.images.append(f)
item = QtGui.QListWidgetItem(f, self.ui.pageList)
item.setStatusTip(f)
self.thread.start()
I'm not sure how to handle this kind of stuff, as I'm just a GUI newbie ;)
Thanks to all.
After many attempts, I finally got it. I can't use a QIcon or QPixmap from within a non-GUI thread, so I had to use a QImage instead, as that transmits fine.
Here's the magic code:
Excerpt from the thumbnailer.py QThread class:
icon = QtGui.QImage(image_file)
self.emit(QtCore.SIGNAL('makeIcon(int, QImage)'), i, icon)
makeIcon() function:
def makeIcon(self, index, image):
item = self.ui.pageList.item(index)
pixmap = QtGui.QPixmap(72, 72)
pixmap.convertFromImage(image) # <-- This is the magic function!
icon = QtGui.QIcon(pixmap)
item.setIcon(icon)
Hope this helps anyone else trying to make an image thumbnailing thread ;)

Resources