Background infinite loop in python - python

Using python 3.8.5, i've try to create a class object which have a inside infinite loop which will update a value that i can have to read later... Unfortunaly, my knowledge on this field are a little poor.
The aims of this trial is to dissociate an app which has a state from the gui i've made...
Here is one not working trial i've made.
import asyncio
from time import sleep
class value_holder:
def __init__(self):
self.value = 0
asyncio.create_task(self.infinite_loop())
async def infinite_loop(self):
while True:
self.value += 1
sleep(3)
v = value_holder()
while True:
print(v.value)
sleep(1)
I'm actually clueless so if someone have any clue or keyword for helping me in this search of solution, i will be very thankful
Best regards

You could work with a timer instead :
import threading
from time import sleep
class value_holder:
value = 0
timer = None
delay = 1
def __init__(self, delay):
self.value = 0
self.delay = delay
def run(self):
self.value += 1
print(self.value)
if self.timer != None: self.start()
def start(self):
self.timer = threading.Timer(self.delay, self.run)
self.timer.start()
def stop(self):
self.timer.cancel()
v = value_holder(3.0)
v.start()
sleep(9) #==> 1, 2, 3
v.stop()
print("Stopped")

Related

Simple Python Pause/Resume

I was trying to pause/resume a task using hotkey, wrote the program bellow which is working fine while hitting Pause hotkey, but resume is not working. I guess I did some logical errors and need your expert advice to overcome that. Here is the script I wrote
import keyboard
class Test:
def __init__(self):
self.run = True
keyboard.add_hotkey("ctrl+alt+p", self.set_run)
keyboard.add_hotkey("ctrl+alt+r", self.set_run_r)
def set_run(self):
self.run = False
def set_run_r(self):
self.run = True
def start(self):
val = 1
while self.run:
val += 1
print("running ", val)
keyboard.wait("esc")
Test().start()
Try this
import keyboard
import sys
class Test:
def __init__(self):
self.val=1
self.run = True
keyboard.add_hotkey("ctrl+alt+p", self.set_run)
keyboard.add_hotkey("ctrl+alt+r", self.set_run_r)
def set_run(self):
self.run = False
def set_run_r(self):
self.run = True
def start(self):
self.val += 1
print(self.val)
return
test= Test()
try:
while True:
if test.run:
test.start()
else:
pass
except KeyboardInterrupt:
sys.exit()

Why doesn't SIGVTALRM trigger inside time.sleep()?

I'm trying to use SIGVTALRM to snapshot profile my Python code, but it doesn't seem to be firing inside blocking operations like time.sleep() and socket operations.
Why is that? And is there any way to address that, so I can collect samples while I'm inside blocking operations?
I've also tried using ITIMER_PROF/SIGPROF and ITIMER_REAL/SIGALRM and both seem to produce similar results.
The code I'm testing with follows, and the output is something like:
$ python profiler-test.py
<module>(__main__:1);test_sampling_profiler(__main__:53): 1
<module>(__main__:1);test_sampling_profiler(__main__:53);busyloop(__main__:48): 1509
Note that the timesleep function isn't shown at all.
Test code:
import time
import signal
import collections
class SamplingProfiler(object):
def __init__(self, interval=0.001, logger=None):
self.interval = interval
self.running = False
self.counter = collections.Counter()
def _sample(self, signum, frame):
if not self.running:
return
stack = []
while frame is not None:
formatted_frame = "%s(%s:%s)" %(
frame.f_code.co_name,
frame.f_globals.get('__name__'),
frame.f_code.co_firstlineno,
)
stack.append(formatted_frame)
frame = frame.f_back
formatted_stack = ';'.join(reversed(stack))
self.counter[formatted_stack] += 1
signal.setitimer(signal.ITIMER_VIRTUAL, self.interval, 0)
def start(self):
if self.running:
return
signal.signal(signal.SIGVTALRM, self._sample)
signal.setitimer(signal.ITIMER_VIRTUAL, self.interval, 0)
self.running = True
def stop(self):
if not self.running:
return
self.running = False
signal.signal(signal.SIGVTALRM, signal.SIG_IGN)
def flush(self):
res = self.counter
self.counter = collections.Counter()
return res
def busyloop():
start = time.time()
while time.time() - start < 5:
pass
def timesleep():
time.sleep(5)
def test_sampling_profiler():
p = SamplingProfiler()
p.start()
busyloop()
timesleep()
p.stop()
print "\n".join("%s: %s" %x for x in sorted(p.flush().items()))
if __name__ == "__main__":
test_sampling_profiler()
Not sure about why time.sleep works that way (could it be using SIGALRM for itself to know when to resume?) but Popen.wait does not block signals so worst case you can call out to OS sleep.
Another approach is to use a separate thread to trigger the sampling:
import sys
import threading
import time
import collections
class SamplingProfiler(object):
def __init__(self, interval=0.001):
self.interval = interval
self.running = False
self.counter = collections.Counter()
self.thread = threading.Thread(target=self._sample)
def _sample(self):
while self.running:
next_wakeup_time = time.time() + self.interval
for thread_id, frame in sys._current_frames().items():
if thread_id == self.thread.ident:
continue
stack = []
while frame is not None:
formatted_frame = "%s(%s:%s)" % (
frame.f_code.co_name,
frame.f_globals.get('__name__'),
frame.f_code.co_firstlineno,
)
stack.append(formatted_frame)
frame = frame.f_back
formatted_stack = ';'.join(reversed(stack))
self.counter[formatted_stack] += 1
sleep_time = next_wakeup_time - time.time()
if sleep_time > 0:
time.sleep(sleep_time)
def start(self):
if self.running:
return
self.running = True
self.thread.start()
def stop(self):
if not self.running:
return
self.running = False
def flush(self):
res = self.counter
self.counter = collections.Counter()
return res
def busyloop():
start = time.time()
while time.time() - start < 5:
pass
def timesleep():
time.sleep(5)
def test_sampling_profiler():
p = SamplingProfiler()
p.start()
busyloop()
timesleep()
p.stop()
print "\n".join("%s: %s" %x for x in sorted(p.flush().items()))
if __name__ == "__main__":
test_sampling_profiler()
When doing it this way the result is:
$ python profiler-test.py
<module>(__main__:1);test_sampling_profiler(__main__:62);busyloop(__main__:54): 2875
<module>(__main__:1);test_sampling_profiler(__main__:62);start(__main__:37);start(threading:717);wait(threading:597);wait(threading:309): 1
<module>(__main__:1);test_sampling_profiler(__main__:62);timesleep(__main__:59): 4280
Still not totally fair, but better than no samples at all during sleep.
The absence of SIGVTALRM during a sleep() doesn't surprise me, since ITIMER_VIRTUAL "runs only when the process is executing."
(As an aside, CPython on non-Windows platforms implements time.sleep() in terms of select().)
With a plain SIGALRM, however, I expect a signal interruption and indeed I observe one:
<module>(__main__:1);test_sampling_profiler(__main__:62);busyloop(__main__:54): 4914
<module>(__main__:1);test_sampling_profiler(__main__:62);timesleep(__main__:59): 1
I changed the code somewhat, but you get the idea:
class SamplingProfiler(object):
TimerSigs = {
signal.ITIMER_PROF : signal.SIGPROF,
signal.ITIMER_REAL : signal.SIGALRM,
signal.ITIMER_VIRTUAL : signal.SIGVTALRM,
}
def __init__(self, interval=0.001, timer = signal.ITIMER_REAL): # CHANGE
self.interval = interval
self.running = False
self.counter = collections.Counter()
self.timer = timer # CHANGE
self.signal = self.TimerSigs[timer] # CHANGE
....

Externally stop a running while loop

I have a cmd.Cmd class command line interpreter that, for example, initializes a self.counter = Counter().
After calling 'start', do_start() will call self.counter.start() and self.counter starts a while loop that counts from 0 to infinity.
Pseudocode example of Counter:
class Counter(object):
def __init__(self):
self.number = 0
self.running = False
def start():
self.running = True
while self.running:
self.number += 1
def status():
return self.number
def stop():
self.running = False
How can I call 'status' in my cmd.Cmd class (which calls do_status()) to get self.counter.status() which will give the current number that has been incremented?
And how can I call 'stop' in my cmd.Cmd class to get self.counter.stop() to stop the while loop.
If you want to do something in parallel you must use threads or multiple processes like this:
import threading
from time import sleep
class Counter(object):
def __init__(self):
self.number = 0
self.running = False
def start(self):
self.running = True
while self.running:
self.number += 1
# add sleep to prevent blocking main thread by this loop
sleep(0.1)
def status(self):
return self.number
def stop(self):
self.running = False
class Cmd(object):
t = None
counter = None
def start(self):
self.counter = Counter()
self.t = threading.Thread(target=self.counter.start)
self.t.start()
def do_status(self):
return self.counter.status()
def stop(self):
self.counter.stop()
# waiting while thread with Counter will finish
self.t.join()
if __name__ == "__main__":
cmd = Cmd()
print "Starting counter"
cmd.start()
sleep(5)
print cmd.do_status()
sleep(2)
print cmd.do_status()
cmd.stop()
print "Counter was stopped"
Output will be:
Starting counter
50
70
Counter was stopped
But if you want to be able communicate with Counter from different application then you must learn about sockets .
if cmd is an instance of Cmd and your using an instance method:
Send the instance to Counter:
def __init__(self, cmd):
self.number = 0
# self.running = False # removed - use self.cmd.status() for control
self.cmd = cmd
Control while using self.cmd:
def start():
while self.cmd.status():
self.number += 1
I expect self.cmd.status() to be blocking (expecting user input, or something like that).

How do I handle multiple EVT_TEXT events in wxPython?

This is one part of a two part question (other part is here)
So here's what I'm looking for: A function which is bound to the EVT_TEXT event of a text control that waits a few seconds, then calls another function at the end of the delay time. Thats easy enough, but, I'd like it to reset the delay time every time a new EVT_TEXT event is generated. The effect I'm looking for is to have a user type into the text control, then after I assume they're done, I run the function described in the other part of this question which spell checks what they've written.
So the simple approach I tried was this:
def OnEdit(self, event):
for i in range(0,3):
print i
time.sleep(1)
However, this just forces a 3 second wait, no matter what. How do I "break in" to this function to reset the counter? Thanks in advance.
EDIT: Turns out the way to do this was with threading. Yippee
The full threading answer, built with the help of this tutorial:
from threading import *
import wx
import time
EVT_RESULT_ID = wx.NewId()
def EVT_RESULT(win, func):
win.Connect(-1, -1, EVT_RESULT_ID, func)
class MyGui(wx.Frame):
def __init__(self):
self.spellchkthrd = None
#lots of stuff
self.input = wx.TextCtrl(self.panel, -1, "", size=(200, 150), style=wx.TE_MULTILINE|wx.TE_LEFT|wx.TE_RICH)
self.Bind(wx.EVT_TEXT, self.OnEdit, self.input)
EVT_RESULT(self, self.OnSplCheck)
def OnEdit(self, event):
if not self.spellchkthrd:
self.spellchkthrd = SpellCheckThread(self)
else:
self.spellchkthrd.newSig()
def OnSplCheck(self, event):
self.spellchkthrd = None
#All the spell checking stuff
class ResultEvent(wx.PyEvent):
def __init__(self):
wx.PyEvent.__init__(self)
self.SetEventType(EVT_RESULT_ID)
class SpellCheckThread(Thread):
def __init__(self, panel):
Thread.__init__(self)
self.count = 0
self.panel = panel
self.start()
def run(self):
while self.count < 1.0:
print self.count
time.sleep(0.1)
self.count += 0.1
wx.PostEvent(self.panel, ResultEvent())
def newSig(self):
print "new"
self.count = 0

Multithreading (?): Manual interference in a loop

I've been looking into a way to directly change variables in a running module.
What I want to achieve is that a load test is being run and that I can manually adjust the call pace or whatsoever.
Below some code that I just created (not-tested e.d.), just to give you an idea.
class A():
def __init__(self):
self.value = 1
def runForever(self):
while(1):
print self.value
def setValue(self, value):
self.value = value
if __name__ == '__main__':
#Some code to create the A object and directly apply the value from an human's input
a = A()
#Some parallelism or something has to be applied.
a.runForever()
a.setValue(raw_input("New value: "))
Edit #1: Yes, I know that now I will never hit the a.setValue() :-)
Here is a multi-threaded example. This code will work with the python interpreter but not with the Python Shell of IDLE, because the raw_input function is not handled the same way.
from threading import Thread
from time import sleep
class A(Thread):
def __init__(self):
Thread.__init__(self)
self.value = 1
self.stop_flag = False
def run(self):
while not self.stop_flag:
sleep(1)
print(self.value)
def set_value(self, value):
self.value = value
def stop(self):
self.stop_flag = True
if __name__ == '__main__':
a = A()
a.start()
try:
while 1:
r = raw_input()
a.set_value(int(r))
except:
a.stop()
The pseudo code you wrote is quite similar to the way Threading / Multiprocessing works in python. You will want to start a (for example) thread that "runs forever" and then instead of modifying the internal rate value directly, you will probably just send a message through a Queue that gives the new value.
Check out this question.
Here is a demonstration of doing what you asked about. I prefer to use Queues to directly making calls on threads / processes.
import Queue # !!warning. if you use multiprocessing, use multiprocessing.Queue
import threading
import time
def main():
q = Queue.Queue()
tester = Tester(q)
tester.start()
while True:
user_input = raw_input("New period in seconds or (q)uit: ")
if user_input.lower() == 'q':
break
try:
new_speed = float(user_input)
except ValueError:
new_speed = None # ignore junk
if new_speed is not None:
q.put(new_speed)
q.put(Tester.STOP_TOKEN)
class Tester(threading.Thread):
STOP_TOKEN = '<<stop>>'
def __init__(self, q):
threading.Thread.__init__(self)
self.q = q
self.speed = 1
def run(self):
while True:
# get from the queue
try:
item = self.q.get(block=False) # don't hang
except Queue.Empty:
item = None # do nothing
if item:
# stop when requested
if item == self.STOP_TOKEN:
break # stop this thread loop
# otherwise check for a new speed
try:
self.speed = float(item)
except ValueError:
pass # whatever you like with unknown input
# do your thing
self.main_code()
def main_code(self):
time.sleep(self.speed) # or whatever you want to do
if __name__ == '__main__':
main()

Categories

Resources