How do I update ipywidget values while running code? - python

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()

Related

Periodical interruptions Python

I sumarize my problem through this piece of code. When I end my program by closing the tkinter main window, I need the whole program ends, but the loops goes on executing until the functions is over. I suppose there is a way to force these functions ends too. I think there is a way to detect the program was ended, so I could end the functions.
import threading
import time
from tkinter import *
def loop1_10():
for i in range(1, 11):
time.sleep(1)
print(i)
def loop1_10_b():
for i in range(1, 11):
time.sleep(2)
print(i)
threading.Thread(target=loop1_10).start()
threading.Thread(target=loop1_10_b).start()
MainWindow = Tk()
MainWindow.mainloop()
The other way to handle this is to make the threads "daemons". A daemon thread will be forcibly closed when the app exits; it doesn't block the app.
threading.Thread(target=loop1_10, daemon=True).start()
threading.Thread(target=loop1_10_b, daemon=True).start()
Note that I'm not saying one is better or worse than the other. Each option has its uses.
Add a protocol, WM_DELETE_WINDOW, to your MainWindow, where you use define a function you defined, on_close() that gets called once the tkinter window is closed.
The on_close() function will redefine the global variable end from False into True, and in each for loop, if the end variable's value is True, return out of them:
import threading
import time
from tkinter import *
def loop1_10():
for i in range(1, 11):
if end:
return
time.sleep(1)
print(i)
def loop1_10_b():
for i in range(1, 11):
if end:
return
time.sleep(2)
print(i)
end = False
def on_closing():
global end
end = True
MainWindow.destroy()
threading.Thread(target=loop1_10).start()
threading.Thread(target=loop1_10_b).start()
MainWindow = Tk()
MainWindow.protocol("WM_DELETE_WINDOW", on_closing)
MainWindow.mainloop()
But there is still a problem with the above code; if the end = True happened right before the time.sleep() call(s), the last time.sleep()(s) will still make the program wait for a second or two before terminating.
To fix this, use time.time() and a while loop to manually check how much time has passed before continuing each for loop:
import threading
import time
from tkinter import *
def loop1_10():
for i in range(1, 11):
old_time = time.time()
while True:
if end:
return
if time.time() - old_time < 1:
continue
break
print(i)
def loop1_10_b():
for i in range(1, 11):
old_time = time.time()
while True:
if end:
return
if time.time() - old_time < 2:
continue
break
print(i)
end = False
def on_closing():
global end
end = True
MainWindow.destroy()
threading.Thread(target=loop1_10).start()
threading.Thread(target=loop1_10_b).start()
MainWindow = Tk()
MainWindow.protocol("WM_DELETE_WINDOW", on_closing)
MainWindow.mainloop()
But do note from this comment by #kindall:
use time.time() and a while loop -- don't do this, it'll use up an entire CPU core waiting for the loop to exit. this will not only eat battery unnecessarily, but since Python only uses one CPU core due to the Global Interpreter Lock, it will make the rest of the program sluggish, too

How can I show an emergent window every hour?

Using tkinter, I'm doing an emergent window to remind me to drink water. At this point it works but now I want the section of def trinke_wasser to repeat every hour. I tried with the time module but in the end, I didnĀ“t know where to write it.
Here is my code:
from tkinter import *
from PIL import Image
from PIL import ImageTk
fenstern = Tk()
fenstern.title("Warnung")
fenstern.geometry("900x600")
datei = Image.open('wasser_pic.png')
bild = ImageTk.PhotoImage(datei)
img = Label(fenstern, image = bild)
img.place(x=0, y=0)
def choice(option):
pop.destroy()
def trinke_wasser():
global pop
fenstern.wm_state('iconic')
pop = Toplevel(fenstern)
pop.title('popup')
pop.geometry("900x600")
back= Label(pop, image = bild)
back.place(x=0, y=0)
rhamen = Frame(pop, bg = "white")
rhamen.pack(pady = 5)
yes = Button(rhamen, text = "YES", command = lambda: choice ("yes"), bg = "orange")
yes.grid(row=0, column=1)
die_Taste = Button(fenstern, text = "Beginnen", command = trinke_wasser )
die_Taste.pack(pady=100)
fenstern.mainloop()
With the time library, there is a sleep function which waits for a number of specified seconds. So if we calculate the number of seconds in an hour or 3600. We can call the sleep function which will wait for exactly 1 hour.
import time
time.sleep(3600)
So if you put the sleep function at the end of your trinke_wasser function. It will tell you to drink water, wait an hour and do it again, over and over
In your current program, you just execute the trinke_wasser() function once upon pressing a key. What you want to do is to open a scheduler function that calls the function once every hour.
But you will have to allow this function to be terminated for good. So, you need a global boolean variable that may be turned off to end the loop. (you could also just end the program execution by force, but that's not nice)
So, what you want to do is to import the time library by adding import time at the beginning of the script.
After fenstern is defined, just add an attribute to this window that is True as long as you don't make it false: fenstern.running = True
Then, you can make your scheduler function:
def nerv_mich(delay = 3600):
while fenstern.running:
trinke_wasser()
time.sleep(3600)
This function will run forever and ask you to drink water ever 3600 seconds.
You just have to adjust the command of the first button:
die_Taste = Button(fenstern, text = "Beginnen", command = nerv_mich)
Now, you just have to turn it off by using a second button just after the definition of yes
fertig = Button(rhamen, text = "Fertig jetzt!", command = lambda: choice ("fertig"), bg = "orange")
yes.grid(row=2, column=1)
Finally, you have to change the function choice() to really end the whole thing after the option passed to it is "fertig":
def choice(option):
pop.destroy()
if option=="fertig": fenstern.running=False
That should work. You can also use fentstern.destroy() to completely end your program at this point. To be honest, you probably don't really need this initial window.

Stop button for breaking while loop within Ipywidgets ecosystem

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]))

Ipython Widgets (how to make a timer)

I made this timer widget that works the way I want, but it locks up the notebook so I can't execute other code cells at the same time.
Here's an example of what I mean:
Any ideas for getting the widget to execute in the background?
In case it helps, here is the code that makes the above widget:
import ipywidgets as widgets
from IPython.display import display, Javascript
from traitlets import Unicode, validate
import time
class Timer(widgets.DOMWidget):
_view_name = Unicode('HelloView').tag(sync=True)
_view_module = Unicode('hello').tag(sync=True)
_view_module_version = Unicode('0.1.0').tag(sync=True)
value = Unicode('00:00:00').tag(sync=True)
def timeit(self, b, limit=180):
#display(self)
hours = 0
mins = 0
secs = 0
for i in range(1,(limit*60+1)):
if i%60 == 0:
if i%3600 == 0:
secs = 0
mins = 0
hours += 1
else:
secs = 0
mins += 1
else:
secs += 1
time.sleep(1)
self.value = '{hour:02}:{minute:02}:{second:02}'.format(hour=hours,minute=mins,second=secs)
def display_timer(timer):
button = widgets.Button(description="Start Timer", button_style='info')
display(button)
display(timer)
button.on_click(timer.timeit)
I found a solution. Read this doc for details.
You use the threading library to make your update function (in my case the timer that updates every second) run separately so you can still execute other code cells.
Also, I tried doing a couple %%timeit 's on simple test functions like this:
[x*=x for i in range(20)]
And it looks like having this timer widget running in the background didn't cause a significant increase in execution time.

Python - run two commands at the same time

I am new to Python and am having trouble with this piece of code:
while true:
rand = random.choice(number)
print(rand)
enter_word = input("Write something: ")
time.sleep(5)
I want to be able to input words in the console while, at the same time, have random numbers appear in the console. But a new number only appears once I input a word. What is the best way to make both these commands run at the same time?
Do I need to make a thread or is there something simpler I can do?
And if I need to make a thread can you please give a little help on how I would create it?
Thanks in advance
This can be achieved by using the multiprocessing module in python, please find the code below
#!/usr/bin/python
from multiprocessing import Process,Queue
import random
import time
def printrand():
#Checks whether Queue is empty and runs
while q.empty():
rand = random.choice(range(1,100))
time.sleep(1)
print rand
if __name__ == "__main__":
#Queue is a data structure used to communicate between process
q = Queue()
#creating the process
p = Process(target=printrand)
#starting the process
p.start()
while True:
ip = raw_input("Write something: ")
#if user enters stop the while loop breaks
if ip=="stop":
#Populating the queue so that printramd can read and quit the loop
q.put(ip)
break
#Block the calling thread until the process whose join()
#method is called terminates or until the optional timeout occurs.
p.join()
To wait for input and to display some random output at the same time, you could use a GUI (something with an event loop):
#!/usr/bin/env python3
import random
from tkinter import E, END, N, S, scrolledtext, Tk, ttk, W
class App:
password = "123456" # the most common password
def __init__(self, master):
self.master = master
self.master.title('To stop, type: ' + self.password)
# content frame (padding, etc)
frame = ttk.Frame(master, padding="3 3 3 3")
frame.grid(column=0, row=0, sticky=(N, W, E, S))
# an area where random messages to appear
self.textarea = scrolledtext.ScrolledText(frame)
# an area where the password to be typed
textfield = ttk.Entry(frame)
# put one on top of the other
self.textarea.grid(row=0)
textfield.grid(row=1, sticky=(E, W))
textfield.bind('<KeyRelease>', self.check_password)
textfield.focus() # put cursor into the entry
self.update_textarea()
def update_textarea(self):
# insert random Unicode codepoint in U+0000-U+FFFF range
character = chr(random.choice(range(0xffff)))
self.textarea.configure(state='normal') # enable insert
self.textarea.insert(END, character)
self.textarea.configure(state='disabled') # disable editing
self.master.after(10, self.update_textarea) # in 10 milliseconds
def check_password(self, event):
if self.password in event.widget.get():
self.master.destroy() # exit GUI
App(Tk()).master.mainloop()
I want to be able to input words in the console while, at the same time, have random numbers appear in the console.
#!/usr/bin/env python
import random
def print_random(n=10):
print(random.randrange(n)) # print random number in the range(0, n)
stop = call_repeatedly(1, print_random) # print random number every second
while True:
word = raw_input("Write something: ") # ask for input until "quit"
if word == "quit":
stop() # stop printing random numbers
break # quit
where call_repeatedly() is define here.
call_repeatedly() uses a separate thread to call print_random() function repeatedly.
you have to run two concurrent threads at the same time in order to get rid of such blocking. looks like there are two interpreters that run your code and each of them executes particular section of your project.

Categories