Stop button for breaking while loop within Ipywidgets ecosystem - python

Let's assume the following problem: we have an Ipywidget button and a progress bar. On clicking the button, a function work() is executed, which merely fills the progress bar until completing it, then reverses the process and empties it out. As it stands, such a function runs continuously. The following code snippet provides the corresponding MWE:
# importing packages.
from IPython.display import display
import ipywidgets as widgets
import time
import functools
# setting 'progress', 'start_button' and 'Hbox' variables.
progress = widgets.FloatProgress(value=0.0, min=0.0, max=1.0)
start_button = widgets.Button(description="start fill")
Hbox = widgets.HBox(children=[start_button, progress])
# defining 'on_button_clicked_start()' function; executes 'work()' function.
def on_button_clicked_start(b, start_button, progress):
work(progress)
# call to 'on_button_clicked_start()' function when clicking the button.
start_button.on_click(functools.partial(on_button_clicked_start, start_button=start_button, progress=progress))
# defining 'work()' function.
def work(progress):
total = 100
i = 0
# while roop for continuous run.
while True:
# while loop for filling the progress bar.
while progress.value < 1.0:
time.sleep(0.01)
i += 1
progress.value = float(i)/total
# while loop for emptying the progress bar.
while progress.value > 0.0:
time.sleep(0.01)
i -= 1
progress.value = float(i)/total
# display statement.
display(Hbox)
The aim is to include "Stop" and "Resume" buttons, so that the while loops are broken whenever the first is clicked, and the execution is resumed when pressing the second one. Can this be done without employing threading, multiprocessing or asynchronicity?

Here's an answer I derived by means of the threading package and based on the background-working-widget example given in https://ipywidgets.readthedocs.io/en/latest/examples/Widget%20Asynchronous.html. It's certainly not optimized, and probably not good-practice-compliant. Anybody coming up with a better answer is welcomed to provide it.
# importing packages.
import threading
from IPython.display import display
import ipywidgets as widgets
import time
# defining progress bar 'progress', start, stop and resume buttons
# 'start_button', 'stop_button' and 'resume_button', and horizontal
# box 'Hbox'.
progress = widgets.FloatProgress(value=0.0, min=0.0, max=1.0)
start_button = widgets.Button(description="start fill")
stop_button = widgets.Button(description="stop fill/empty")
resume_button = widgets.Button(description="resume fill/empty")
Hbox = widgets.HBox(children=[start_button, stop_button, resume_button, progress])
# defining boolean flags 'pause' and 'resume'.
pause = False
restart = False
# defining 'on_button_clicked_start()' function.
def on_button_clicked_start(b):
# setting global variables.
global pause
global thread
global restart
# conditinoal for checking whether the thread is alive;
# if it isn't, then start it.
if not thread.is_alive():
thread.start()
# else, pause and set 'restart' to True for setting
# progress bar values to 0.
else:
pause = True
restart = True
time.sleep(0.1)
restart = False
# conditional for changing boolean flag 'pause'.
if pause:
pause = not pause
# defining 'on_button_clicked_stop()' function.
def on_button_clicked_stop(b):
# defining global variables.
global pause
# conditional for changing boolean flag 'pause'.
if not pause:
pause = not pause
# defining 'on_button_clicked_resume()' function.
def on_button_clicked_resume(b):
# defining global variables.
global pause
global restart
# conditional for changing boolean flags 'pause' and 'restart'
# if necessary.
if pause:
if restart:
restart = False
pause = not pause
# call to 'on_button_clicked_start()' function when clicking the button.
start_button.on_click(on_button_clicked_start)
# call to 'on_button_clicked_stop()' function when clicking the button.
stop_button.on_click(on_button_clicked_stop)
# call to 'on_button_clicked_resume()' function when clicking the button.
resume_button.on_click(on_button_clicked_resume)
# defining the 'work()' function.
def work(progress):
# setting global variables.
global pause
i = 0
i_m1 = 0
# setting 'total' variable.
total = 100
# infinite loop.
while True:
# stop/resume conditional.
if not pause:
# filling the progress bar.
if (i == 0) or i > i_m1 and not pause:
time.sleep(0.1)
if i == i_m1:
pass
else:
i_m1 = i
i += 1
progress.value = float(i)/total
# emptying the progress bar.
if (i == 101) or i < i_m1 and not pause:
time.sleep(0.1)
if i == i_m1:
pass
else:
i_m1 = i
i -= 1
progress.value = float(i)/total
else:
if restart:
i = 0
i_m1 = 0
# setting the thread.
thread = threading.Thread(target=work, args=(progress,))
# displaying statement.
display(Hbox)

After trying using asynchronous, I run into too many problems. Much better approach is to use generator approach, also mentioned in documentation https://ipywidgets.readthedocs.io/en/latest/examples/Widget%20Asynchronous.html
Here is the code I came up with
from functools import wraps
from IPython.core.display import display
import ipywidgets as widgets
def yield_for_change(widget, attribute):
def f(iterator):
#wraps(iterator)
def inner():
i = iterator()
def next_i(change):
try:
print([w.description for w in widget])
i.send(change)
except StopIteration as e:
for w in widget:
w.unobserve(next_i, attribute)
for w in widget:
w.on_click(next_i)
w.observe(next_i, attribute)
# start the generator
next(i)
return inner
return f
btn_true = widgets.Button(description="True",style={'button_color':'green'} )
btn_false = widgets.Button(description="False",style={'button_color':'red'})
btn_list = [btn_true, btn_false]
buttons = widgets.HBox(btn_list)
value_loop = widgets.Label()
out = widgets.Output()
#yield_for_change(btn_list, 'description')
def f():
for i in range(10):
print('did work %s'%i)
x = yield
print('generator function continued with value %s'%x)
f()
display(widgets.VBox([buttons, out]))

Related

jupyter-lab: How to stop a long-running function with an ipywidget button?

In jupyter-lab I have a long running function long_runner() doing some heavy numeric computation. I would like to be able to stop this function by clicking on an ipywidget button. Interrupting the kernel is not an option since other parts of the GUI should continue working.
Here is a code example with a button which sets a stop flag. This stop flag is used in long_runner to break out of the loop. When long_runner is active, the Button seems to be reactive to clicks, but the associated function set_stop_flag is only executed after long_runner has finished normally and thus no intermediate stop is possible.
I looked at some examples with asyncio but could not find something fitting this use-case with a heavy-compute function.
How could the desired behavior be acchieved?
from ipywidgets import Button
stop_flag = 0
def set_stop_flag(b):
global stop_flag
print("stop flag activated")
stop_flag = 1
b=Button(description="stop")
b.on_click(set_stop_flag)
display(b)
def long_runner():
x=0
print("starting ...")
for i in range(100000000):
if stop_flag==1:
print("stop=",stop_flag)
break
x += i*i
print("stopping",i)
return(x,i)
print(long_runner())
Here is the program output when the stop-button is clicked during the execution of long-runner:
following the first comment (thanks!), specifically this [link][1] I was able to modify the example such that one can now interrupt the long_runner with a button. The key is to start key_runner in a thread. Below is the program followed by the output when clicking the button after a few iterations. The sleep(1.0) is only made to limit the output freqency and is non-essential.
import threading
import numpy as np
from ipywidgets import Button
import time
stop_flag = 0
def set_stop_flag(b):
global stop_flag
print("stop flag activated")
stop_flag = 1
b=Button(description="stop")
b.on_click(set_stop_flag)
display(b)
def long_runner():
x=0
print("starting ...")
for i in range(100000000):
time.sleep(1.0)
print(f"x={x} i={i} stop_flag={stop_flag}")
if stop_flag==1:
print("stopped at i=",i)
break
x += i*i
print("stopping")
return(x,i)
thread = threading.Thread(target=long_runner)
thread.start()
Output:
(imagine button here)
starting ...
x=0 i=0 stop_flag=0
x=0 i=1 stop_flag=0
x=1 i=2 stop_flag=0
x=5 i=3 stop_flag=0
x=14 i=4 stop_flag=0
x=30 i=5 stop_flag=0
stop flag activated
x=55 i=6 stop_flag=1
stopped at i= 6
stopping```
[1]: https://discourse.jupyter.org/t/how-to-make-start-and-stop-buttons/8331/4?u=fomightez
[2]: https://i.stack.imgur.com/1Bchu.png

How do I update ipywidget values while running code?

So I want to create a simple UI in Jupyter notebook where:
A counter "number" is incremented every second
If the checkbox "pause" is checked, "number" is not incremented
If the button "trigger" is pressed, "number" is decremented, regardless of pause status
So far I tried a few variants of the code below but it doesn't work; it seems like the widget values aren't updating when running the while loop. Is there a way to fix it or another way to do this? Thanks!
import ipywidgets as widgets
import time
from IPython.display import display, clear_output
btn = widgets.Button(description = "Trigger")
pause = widgets.Checkbox(value = False, description = "Paused?")
number = widgets.Label("0")
wid = widgets.VBox([btn, number, pause])
display(wid)
def triggered(b):
number.value = str(int(number.value) - 1)
btn.on_click(triggered)
while True:
time.sleep(1)
while (pause.value == True):
time.sleep(3)
number.value = str(int(number.value) + 1)
As mentioned by ac24, the trick is to run your counter function in a different thread. You can use the threading library to do that. Below, I defined a function counter and launched it in a different thread. That way, the user is still able to interact with the widgets while the counter function is running.
An important thing to keep in mind is that once you launched your thread there isn't many elegant ways to kill it. This means that it's better to set up a total_duration variable rather than using while True.
See code below:
import ipywidgets as widgets
import time
from IPython.display import display, clear_output
import threading
btn = widgets.Button(description = "Trigger")
pause = widgets.Checkbox(value = False, description = "Paused?")
number = widgets.Label("0")
wid = widgets.VBox([btn,number,pause])
display(wid)
def triggered(b):
number.value = str(int(number.value) - 1)
btn.on_click(triggered)
def counter(number,pause,total_duration):
for t in range(total_duration):
if not pause.value:
time.sleep(1)
number.value = str(int(number.value) + 1)
elif pause.value:
time.sleep(3)
total_duration=60
thread = threading.Thread(target=counter, args=(number,pause,total_duration,))
thread.start()

GUI with pyqtgraph never refresh

I have been working on a GUI for the beagle bone black that launch a thread when a button is clicked and starts to get data through the SPI.
This function is inside a class called Scanner(QObject) and runs in a different thread when the Start button is clicked.
def scan (self):
thread_name = QThread.currentThread().objectName()
self.sig_msg.emit('Scanning '+thread_name)
for step in range(nsamples):
data = self.read_reg(reg[thread_name])
self.sig_data.emit(step, data)
QThread.currentThread().msleep(50)
app.processEvents()
if self.__abort:
self.sig_msg.emit('scan stopped by user')
break
self.sig_done.emit(thread_name)
sig_msg is a pyqtsignal connected to the following function inside the GUI thread.
#pyqtSlot(int, int)
def on_scaner_data(self, t: int, y: int):
app.processEvents()
self.debugBox.insertPlainText('t: '+str(t)+'y: '+str(y)+'\n')
self.debugBox.ensureCursorVisible()
self.MainGraph.update_fig(t,y)
And finally the MainGraph.update_fig() is called. Inside that function i have used setData(self.datat,self.datay) and app.processEvents() for update the graph, but nothing changes. If i run plot(self.datat,self.datay) instead it redraws the graph but causes a huge performance hit.
class DynamicPlotter(PlotWidget):
def __init__(self,parent=None):
PlotWidget.__init__(self)
self.setParent(parent)
# Use getPlotItem() to get the PlotItem inside PlotWidget.
self.pitem = self.getPlotItem()
#now pitem is our PlotItem
self.pitem.curve=self.pitem.plot()
#curve is a new PlotDataItem added by PlotItem.plot()
self.datat = [1,2]
self.datay = [1,2]
self.pitem.curve.setData(self.datat,self.datay)
#this graph works fine
self.datat = []
self.datay = []
def update_fig(self,t:int,y:int):
self.datat.append(t)
self.datay.append(y)
#it works
self.pitem.curve=self.pitem.plot(self.datat,self.datay)
#it doesn't
self.pitem.curve.setData(self.datat,self.datay)
app.processEvents()
print (self.datat)
log.debug(str(t)+str(y))
def reset_figure(self):
log.debug('clean graph')
self.clear()
I have been following this example from the pyqtplot and my idea was do something similar inside my GUI.
import initExample
from pyqtgraph.Qt import QtGui, QtCore
import numpy as np
import pyqtgraph as pg
from pyqtgraph.ptime import time
app = QtGui.QApplication([])
p = pg.plot()
p.setWindowTitle('pyqtgraph example: PlotSpeedTest')
p.setRange(QtCore.QRectF(0, -10, 5000, 20))
p.setLabel('bottom', 'Index', units='B')
curve = p.plot()
data = np.random.normal(size=(50,5000))
ptr = 0
lastTime = time()
fps = None
def update():
global curve, data, ptr, p, lastTime, fps
curve.setData(data[ptr%10])
ptr += 1
now = time()
dt = now - lastTime
lastTime = now
if fps is None:
fps = 1.0/dt
else:
s = np.clip(dt*3., 0, 1)
fps = fps * (1-s) + (1.0/dt) * s
p.setTitle('%0.2f fps' % fps)
app.processEvents() ## force complete redraw
timer = QtCore.QTimer()
timer.timeout.connect(update)
timer.start(0)
I have been reading the documentation and right know I'm not sure where is/are the problems. I bet for the threads or the event loop handler but i don't know.
Which are the critical points that i have to review?
Any clue?
Thank you.
After a while i have found the problem by myself.
I fixed the problem changing the way that i was using to reset the graph and stopping the scan thread until elements are plot.
Changes on reset function. self.clear() remove the traces on the PlotWidget and that wasn't what i needed.
def reset_figure(self):
log.debug('clean graph')
self.datat =[]
self.datay=[]
self.pitem.curve.setData(self.datat,self.datay)
Scan was modified to stop while the data is plotted in the other thread. sync_to_plot stop the thread execution until self._wait=False. This value is changed by wait_state.
def scan (self):
thread_name = QThread.currentThread().objectName()
#thread_id = str(QThread.currentThreadId())#review
self.sig_msg.emit('Scanning '+thread_name)
for step in range(nsamples):
data = self.read_reg(reg[thread_name])
self.sig_data.emit(step, data)
#pause while plot values
self.sync_to_plot()
if step % refrate == 0:
log.debug("%5d : %d" % (step, data) )
if self.__abort:
self.sig_msg.emit('scan stoped by user')
break
self.sig_done.emit(thread_name)
def sync_to_plot(self):
self._wait=True
while self._wait:
log.debug("waiting")
QThread.currentThread().msleep(1)
app.processEvents()
def wait_state(self, stat):
self._wait=stat
With that done the last change was on the on_scaner_data that unblocks the thread that is waiting on sync_to_plot.
#pyqtSlot(int, int)
def on_scaner_data(self, t: int, y: int):
app.processEvents()
self.debugBox.insertPlainText('t: '+str(t)+'y: '+str(y)+'\n')
self.debugBox.ensureCursorVisible()
self.MainGraph.update_fig(t,y)
for thread, scaner in self.__threads:
scaner.wait_state(False)
log.debug("scanner false")

Function not calling and how to wait for click in tkinter loop

I've hit a dead end at the moment - firstly when you click one of the dynamic buttons, the function call is made, and it does return the name of the button clicked. However the fEndDay function it's supposed to call as well doesn't appear to run.
EDIT: The day is now running. Just relaunched Liclipse and it started working. No explanation. However, the button wait issue remains.
I'm also a little stuck at the moment. In essence I want:
While current day < total days....
Run a daily event, updating the screen objects.
Take a choice via button click.
Increase the current day.
However, the day loop stops the screen from displaying (i.e. processing loop). I guess if there's code that pushes the object display up and sits in an infinate loop, which is broken by the button click, that would do. Other ideas? Current code below.
#!/usr/bin/python
# Reminder to self - lots to add. Include a function to reset the text content
# and populate with day number, score summary etc as a template. We can then
# add to it.
#################### IMPORT MODULES WE NEED ############################
import time # For sleep delays
from random import randint # for random numbers
from _ast import While # while loops
import tkinter as Tkinter # this one handles windows and buttons etc
from tkinter import * # skip the tkinter prefix (constants etc)
# import tkmessagebox # Python 2 alternative!
from tkinter import messagebox as tkMessageBox # Python 3
import sys # for quit when added ie sys.exit()
from functools import partial # So we can create lists of buttons & commands
import time # threading support - check events while waiting
import concurrent.futures # threading - think i'll be needing all this
################## CREATE A NEW CLASS (CONTAINER) ############################
class CrazyCoder (Tkinter.Tk):
# When the class is created, the function fInitialise is run, below.
def __init__(self,parent):
Tkinter.Tk.__init__(self,parent)
self.parent = parent
self.fInitialize() # State here any functions to run on creation
################################ FUNCTION ################################
# Set up variables etc...
def fInitialize(self):
########################### VARIABLES ###############################
# Overkill here probably but will revisit later! Listed / initialised
# here to simplify searching later on!
# Could pass most of theses but will keep it simple and update globally
# to start with
self.vDayNumber = 1 # What the current day is
self.vFinalDay = 10 # The last day of our game
self.vPlayerName = '' # To hold the players name
self.vGameName = '' # To hold the game name
self.vChoice = '' # To hold the user choices clicked
self.vRandom = 0 # To hold random numbers!
self.vGameplay = 0 # SCORES: Current gameplay score
self.vGraphics = 0 # SCORES: Current graphics score
self.vSound = 0 # SCORES: current sound score
self.vBugs = 0 # SCORES: current bug score'
self.vBackColor = 'grey' # The background colour of our app
self.vDynamic_Buttons = [] # To hold button objects each "screen"
self.vEntryBox = [] # To hold text entry box objects
self.vTextEntry = '' # Stores user text entry value temporarily
self.vAvailableButtons = [] # To hold a list of AVAILABLE buttons/event
########################## APP SETUP ################################
self.title('Crazy Coder') # set window title
self.geometry("500x500") # set screen size
self.configure(background=self.vBackColor) # set background colour
# Add a "Title box"
self.vTitle = Tkinter.Label(self,text='Crazy Coder')
self.vTitle.configure(background=self.vBackColor)
self.vTitle.pack(side=TOP,padx=10,pady=10)
# Add a picture box (gif supported)
self.vImage = PhotoImage(file="PUG.gif")
self.vPicture=Label(self,image=self.vImage)
self.vPicture.image=self.vImage
self.vPicture.pack()
# Add the main text box
self.vMessageText = '''
This is where your day number goes
Scores go here too
Event details go here too'''
self.vMessage = Tkinter.Label(self,text=self.vMessageText)
self.vMessage.configure(background=self.vBackColor)
self.vMessage.pack(side=TOP,padx=10,pady=10)
# While loop does not work - there is no concept of
# "display current buttons / screen and wait for click event"
#while self.vDayNumber <= self.vFinalDay:
self.vChoice = '' # Clear it ready to take a user choice each day
print('DEBUG: On Main screen, starting now')
self.vRandom = randint(1,100)
if self.vDayNumber == 1:
self.fWelcomeScreen() # Set up the welcome screen
elif self.vRandom >= 0:
self.fEvent1() # Kick off event 1
############################# FUNCTION #################################
# Sets the message on the main text box to whatever you put in brackets
def fSetText(self,TextMessage):
global vMessageText
self.vMessageText = TextMessage
self.vMessage['text'] = self.vMessageText # This updates the text box
############################# FUNCTION #################################
# Sets the image on the main picture box to whatever you put in brackets
def fSetImage(self,ImageName):
global vImage
self.vImage = PhotoImage(file=ImageName) # Example "PUG2.gif"
self.vPicture['image']=self.vImage # This updates the image box
############################# FUNCTION #################################
# Add a new Entry box to our screen. Supports multiple uses
def fAddEntryBox(self):
self.vNewBox = Entry(width=20)
self.vEntryBox.append(self.vNewBox)
self.vNewBox.pack(side=TOP,padx=10,pady=10)
self.vNewBox.focus()
############################# FUNCTION #################################
# Remove the Entry Boxes
def fDeleteEntryBoxes(self):
for each_box in self.vEntryBox:
each_box.destroy()
############################# FUNCTION #################################
# Read from the requested box number, cutting off the Enter at the end
def fReadEntryBox(self,BoxNumber): #BoxNumber 0 is the first box, 1 next
global vTextEntry
vTextEntry=self.vEntryBox[BoxNumber].get()
############################# FUNCTION #################################
# Handles the the day passing by
def fEndDay(self):
global vPlayerName, vDayNumber
self.vDayNumber = self.vDayNumber + 1
print("This print isn't running either!")
############################# FUNCTION #################################
# A simple step to take a choice from the user - used for button code below
def fMakeChoice(self,value):
global vChoice, vDayNumber
self.fEndDay()
self.vChoice = value
print('Just Clicked:',self.vChoice, "and it's day ", self.vDayNumber)
print('But fEndDay should have just run and increased it to 2!')
############################# FUNCTION #################################
# Add buttons to the screen, based on vAvailableButtons
def fAddButtons(self):
global vAvailableButtons # Shouldn't need this here but...
for each_button in self.vAvailableButtons:
# Important: the Lambda section takes the CURRENT value of the variable
# and stores is in the command string. Without it, all the buttons
# when clicked would do the same as the last button created! daft eh.
vNewButton = Tkinter.Button(self, text=each_button, command=lambda v=each_button: self.fMakeChoice(v))
self.vDynamic_Buttons.append(vNewButton)
vNewButton.pack(side= BOTTOM, padx = 10, pady = 10)
############################# FUNCTION #################################
# Clear the buttons out ie before drawing new ones
def fDeleteButtons(self):
for each_button in self.vDynamic_Buttons:
each_button.destroy()
############################# FUNCTION #################################
# Pop up message box
def fMessage(self,Message):
self.tkMessageBox.showinfo("News", Message)
#*********************************************************************#
#********************* EVENTS SECTION ***************************#
#*********************************************************************#
# We'll define a function here for each "daily event"
# The section will be responsible for:
# 1) Updating the screen image (if necessary)
# 2) Updating the text box content
# 3) Adding entry boxes and buttons as required
# 4) Ending the day (there will be one decision per day for now)
############################# EVENT #################################
# The welcome screen
def fWelcomeScreen(self):
global vAvailableButtons
self.vTextMessage = '''
Yawn. You wake up.
Whats your name?
'''
self.fSetText(self.vTextMessage)
time.sleep(1) # delays for 1 second
self.fAddEntryBox() # Add an entry box for a name
self.vAvailableButtons = ['Ok']
self.fAddButtons() # vChoice set on click, and day ended
############################# EVENT #################################
# A random event
def fEvent1(self):
global vAvailableButtons
self.vTextMessage = '''
This is an event. What will you do?
'''
self.fSetText(self.vTextMessage)
time.sleep(1) # delays for 1 second
self.vAvailableButtons = ['Ok']
self.fAddButtons() # vChoice set on click, and day ended
# Start the program
if __name__ == "__main__":
app = CrazyCoder(None)
app.mainloop()
The fact that you want to run some function once a day is no different than wanting an animation to run 30 frames per second. You effectively want an "animation" that runs 1 fpd (one frame per day).
The way to accomplish that is to create a function that should run once a day.
def do_once_a_day():
...
Next, create a function that will call that function once a day:
def call_daily(self):
self.do_once_a_day()
if self.vDayNumber <= self.vFinalDay:
root.after(1000*60*60*24, self.call_daily)
This will cause do_once_a_day to be called once every 24 hours. You can then do whatever you want inside that function.

multithreading in Python: extremely high CPU consumption

Below is the code of a simple timer that prints out a new number in the command line every 0.1 sec, and -- simultaneously -- awaits for a keypress (Enter). As soon as Enter is pressed, the timer is switched into pause, stops printing and waits for another Enter keypress that would resume counting / printing.
The problem I face is that while in the 'pause' state, CPU consumption grows to almost 100% (and seems to be below 1% while running / printing out).
Why is it? What am I doing wrong?
Here goes the code:
import time, thread, sys
time_passed = 0
l = []
trig = True
def input(l):
""" Waits for a keypress """
raw_input()
l.append(None)
def timer():
""" Prints out a number every millisecond """
global time_passed
time.sleep(.1)
time_passed += 1
sys.stdout.write(format(time_passed)+"\r")
sys.stdout.flush()
def running():
""" Toggles play/pause state if Enter is pressed """
global trig, l
while trig:
timer()
if l:
l = []
trig = False
return trig, l
def stopped():
""" Toggles pause/play state if Enter is pressed """
global trig, l
while not trig:
if l:
l = []
trig = True
return trig, l
def main():
""" Waits for a keypress while either running or stopping the timer """
global l, trig
while True:
thread.start_new_thread(input, (l,))
if trig: # The timer is running
running()
else: # The timer is stopped
stopped()
main()
Thank you.
Well, if its in a "stopped" state you run this:
def stopped():
""" Toggles pause/play state if Enter is pressed """
global trig, l
while not trig:
if l:
l = []
trig = True
return trig, l
this loop has no pause/wait. So it runs at full speed without getting back to system, while within the running you do call time.sleep. Putting time.sleep(0.1) before the if will make it work as you wish.
Also a friendly personal note, you could refactor it a little bit. Two functions with the same description working on globals make it a little bit hard to understand =).
I'd make something like:
import time, thread, sys, threading
time_passed = 0
l = []
def input(l):
""" Waits for a keypress """
raw_input()
l.append(None)
def timer():
""" Prints out a number every millisecond """
global time_passed
time_passed += 1
sys.stdout.write(format(time_passed)+"\r")
sys.stdout.flush()
def running(state):
""" Toggles play/pause state if Enter is pressed """
global l
while True:
time.sleep(.1)
if state:
timer()
if l:
l = []
state = not state
return state
def main():
""" Waits for a keypress while either running or stopping the timer """
global l
state = True
while True:
thread.start_new_thread(input, (l,))
state = running(state)
main()
and I would think how to signal the toggle better. I somehow don't like the idea of returning from the function, you don't need really to return from running, just flip the state. But it is just personal preference I guess.

Categories