tkinter after calling function with parameters causing function to hang - python

This is for a game of life app and using after to slow the animation of cells through the stages of birth, life, death, etc.
Couple issues with after:
1.I've been researching Tkinter extensively and this post captures the most clear instructions I have found regarding after with parameters, however, my application of Tkinter is not working - based on comments already received it's probably not the after but that's where the symptoms are appearing?
2.Basically the after does not appear to work at all when I don't put the parameters into parentheses inside the after (ex. widget.after(200, self.my_function, parameter 1, parameter 2, ....) doesn't iterate. However, when I do the same but enclose the parameters it iterates as intended (ex. widget.after(200, self.my_function(parameter 1, parameter 2, ....)).
3.However, when run with the parameters in parentheses, the after hangs. The list in the code below contains 81 items and not coincidentally, the function hangs for 16.2 seconds...
Code is as follows:
def color_cells(
self,
cells,
repeater,
temp_cells=0,
counter=0,
after_id=None
):
global paused
if temp_cells != 0:
self.repeat_colors(temp_cells)
temp_cells.clear()
temp_cells = 0
for key in cells:
if cells[key][0] == 'emerging':
cells[key] = 'active', colors['active'][1]
if cells[key][0] == 'dying':
cells[key] = 'inactive', colors['inactive'][1]
counter = 0
if repeater and not paused:
print("repeater is ", repeater, " and paused is ",paused)
self.id_changes(cells)
else:
self.closeoutGame()
else:
try:
print("made it to the else in color_cells and repeater is ",repeater, " and length of temp cells list is ", len(temp_cells))
except:
print("made it to the else in color_cells and repeater is ",repeater, " and temp cells is empty")
for key in cells:
color_rectangle = self.canvas_manager.find_withtag(key)
self.canvas_manager.itemconfigure(color_rectangle, fill = cells[key][1])
self.canvas_manager.update()
def repeat_colors(self, temp_cells, counter=0):
print("starting repeat colors and the temps cells len is ", len(temp_cells), " and the counter is ",counter)
if counter < len(temp_cells):
color_rectangle = self.canvas_manager.find_withtag(temp_cells[counter][0])
self.canvas_manager.itemconfigure(color_rectangle, fill = temp_cells[counter][1])
self.canvas_manager.update()
counter = counter + 1
root.after(200, self.repeat_colors(temp_cells, counter))
Bryan, you had asked for an example of the error. To show the error I added some print statements to the calling function and then explained where the 16.2 second period of inactivity starts:
starting repeat colors and the temps cells len is 81 and the counter is 0
starting repeat colors and the temps cells len is 81 and the counter is 1
starting repeat colors and the temps cells len is 81 and the counter is 2
...
starting repeat colors and the temps cells len is 81 and the counter is 79
starting repeat colors and the temps cells len is 81 and the counter is 80
starting repeat colors and the temps cells len is 81 and the counter is 81
...hangs for the 16.2 seconds, equal to 200 ms x 81 iterations
I'm a hobbyist and have no formal training and so I'm sure I'm overlooking something basic here, including how to best research on my own. But I appreciate any advice.

I would suggest neither:
root.after(200, self.repeat_colors(temp_cells, counter))
nor:
root.after(200, lambda x=counter: self.repeat_colors(temp_cells, x))
but rather:
root.after(200, self.repeat_colors, temp_cells, counter)
This is a misunderstanding of after() I see often and had myself. The definition of after():
after(ms, func=None, *args)
Call function once after given time.
MS specifies the time in milliseconds. FUNC gives the
function which shall be called. Additional parameters
are given as parameters to the function call. Return
identifier to cancel scheduling with after_cancel.
The code you posted with the above fix and other tweaking -- unfortunately hard to do well without a MCVE:
def color_cells(self, cells, repeater, temp_cells=None, counter=0):
if temp_cells is not None:
self.repeat_colors(temp_cells)
temp_cells.clear()
temp_cells = None
for key in cells:
if cells[key][0] == 'emerging':
cells[key] = 'active', colors['active'][1]
if cells[key][0] == 'dying':
cells[key] = 'inactive', colors['inactive'][1]
counter = 0
if repeater and not paused:
print("repeater is", repeater, "and paused is", paused)
self.id_changes(cells)
else:
self.closeoutGame()
else:
print("Made it to the else in color_cells and repeater is", repeater, "and temp cells is empty")
for key in cells:
color_rectangle = self.canvas_manager.find_withtag(key)
self.canvas_manager.itemconfigure(color_rectangle, fill=cells[key][1])
self.canvas_manager.update()
def repeat_colors(self, temp_cells, counter=0):
print("Starting repeat colors and the temps cells len is", len(temp_cells), "and the counter is", counter)
if counter < len(temp_cells):
color_rectangle = self.canvas_manager.find_withtag(temp_cells[counter][0])
self.canvas_manager.itemconfigure(color_rectangle, fill=temp_cells[counter][1])
self.canvas_manager.update()
counter += 1
root.after(200, self.repeat_colors, temp_cells, counter)

You problem would be the infinite loop caused by root.after(200, self.repeat_colors(temp_cells, counter)). Instead you need to pass your self.repeat_colors as a lambda.
So what is happening is the self.repeat_colors(temp_cells, counter) is being called instantly instead of waiting the 200 seconds. So instead create a lambda function that will wait until the set time to activate.
Keep in mind for lambda expressions if you have a value that changes you need to define it in the lambda. So for the counter you need to do something like x=counter so the lambda is sure to use the correct updated value instead. This normally affects things like loops that create lambda expressions and probably does not matter in this particular case but is a good habit to practice for when it does matter.
Change this:
root.after(200, self.repeat_colors(temp_cells, counter))
To this:
root.after(200, lambda x=counter: self.repeat_colors(temp_cells, x))

Related

How do I make rolling text for a loading screen?

I want to make a loading screen that does this, but replaces the current line:
LOADING
OADINGL
ADINGLO
DINGLOA
INGLOAD
...
I want to be able to control the number of letters it prints at once. What I tried:
from itertools import cycle
from time import sleep
itr = cycle('LOADING')
for i in range(10):
sleep(0.3)
print('\r', ''.join(next(itr)))
But the output is:
L
O
A
D
I
N
G
L
O
A
You will need to use the end keyword in print to avoid printing each message to a new line. I recommend the approach where you build the string to be displayed on each iteration and then display it. I don't like the cycle approach because you're unable to index into the cycle object very easily. Instead, we can use regular string indexing along with the modulus operator to ensure we don't go out of bounds and can still "loop" over the string over and over.
import time
def scrolling_text(msg, n_chars):
"""
Displays scrolling text of whatever 'msg' and ensures that only n_chars
are shown at a time. If n_chars is greater than the length of msg, then
the string displayed may "loop" around.
Inputs:
msg: str - The message to display
n_chars: int - The number of characters to display at a time
Outputs: Returns None, displays the scrolling text indefinitely.
"""
len_msg = len(msg)
counter = 0
while True:
displayed = ""
for char in range(n_chars):
displayed += msg[(counter+char)%len_msg]
print(f"\r{displayed}", end="")
time.sleep(0.05)
counter = (counter + 1) % len_msg
return
scrolling_text("LOADING", 25)
Each iteration of the while True loop will build the string to be displayed (inside the nested for loop) and then display it. At the end of the while loop, it would be enough to have counter += 1 however for a long-running script, you might end up with counter being unnecessarily large. By letting counter = (counter + 1) % len_msg, we can ensure counter never gets higher than the length of the message.
Straightforwardly with Python slicing:
from time import sleep
from sys import stdout
s = 'LOADING'
for i in range(10):
sleep(0.3)
if i > len(s): i = i - len(s)
stdout.write('\r' + ''.join(s[i:] + s[:i]))

How can I fix a function that does not take in what it returned?

have tried to use the return feature so when I call the Function it will use the last generated generation of solutions, but instead, it just uses the randomly generated ones that I used for the first generation and doesn't use the newest, this is an attempt of the Genetic algorithm in python on countdown to find a solution using "*,/,+,-" as operators and six numbers that are generated.
Forgive me if this was an overly simple mistake but I couldn't find anything online as to why it wouldn't use the latest Generation/return properly.
Mutate, SecondGen and CurrentPopFitness are both lists of lists, ([[]]) Mutate is ran through a cross over algorithm and mutation is done to it and appended to SecondGen, the Fitness scores are then calculated and appended to CurrentPopFitness along with the solution that gave that fitness, the 50 best solutions are then appended to SecondGen and Mutate is set to equal it which it does as I checked via the print at the end, however when it goes onto the next call of the function Mutate is back to being what it was at the start of the program.
Mutate is made to be 50 long just before the function ends.
I have checked that the second generation at the end is different from what it was for the first generation, However, Mutate (which the contents of the second generation are in at the end) still ends up being the first generation I generated outside of the function; when calling it for the 2nd time and every time after.
Target = random.randint(101,1000)
track2 = 0
Mutate = [[5/6*24-4+3+2][2/5+100*50-7-8]....]
def OffspringMutation(SecondGen,Mutate):
print(len(Mutate))
for x in range(50):
if track2 >= 1:
SecondGen = SecondGen + 1*[[]]
SecondGen[track2].append(CurrentPopFitness[x][1])
SecondGen[track2].append(CurrentPopFitness[x][2])
SecondGen[track2].append(CurrentPopFitness[x][3])
SecondGen[track2].append(CurrentPopFitness[x][4])
SecondGen[track2].append(CurrentPopFitness[x][5])
SecondGen[track2].append(CurrentPopFitness[x][6])
SecondGen[track2].append(CurrentPopFitness[x][7])
SecondGen[track2].append(CurrentPopFitness[x][8])
SecondGen[track2].append(CurrentPopFitness[x][9])
SecondGen[track2].append(CurrentPopFitness[x][10])
SecondGen[track2].append(CurrentPopFitness[x][11])
track2 += 1
Mutate = SecondGen
return(Mutate)
TruFal = True
while TruFal != False:
stop = input("Type X to stop the Genetic Algorithm, otherwise press enter")
if stop == "X":
TruFal = False
OffspringMutation(SecondGen,Mutate)
The expected result is for len(Mutate) to be roughly 60 long at the start (i made it roughly 60 long). Then on the second call of the function(the second generation) len(Mutate) should be 50 long and the contents different from what they were set to at the start.
You're not recording the return value of OffspringMutation. Although you pass the reference to Mutate to the function, this reference is stored in the local variable of the same name you gave it. Therefore any actions to its contents will change the original eg: append, slicing, delete, etc. But by doing Mutate = SecondGen you are not editing the contents of the reference, you are telling the local variable to point to a different reference, unaffecting the original. By this logic something like Mutate[:] = SecondGen (slicing) will change the original, but this is bad practice for a function.
Target = random.randint(101,1000)
track2 = 0
Mutate = [[5/6*24-4+3+2][2/5+100*50-7-8]....]
def OffspringMutation(SecondGen,Mutate):
print(len(Mutate))
for x in range(50):
if track2 >= 1:
SecondGen = SecondGen + 1*[[]]
SecondGen[track2].append(CurrentPopFitness[x][1])
SecondGen[track2].append(CurrentPopFitness[x][2])
SecondGen[track2].append(CurrentPopFitness[x][3])
SecondGen[track2].append(CurrentPopFitness[x][4])
SecondGen[track2].append(CurrentPopFitness[x][5])
SecondGen[track2].append(CurrentPopFitness[x][6])
SecondGen[track2].append(CurrentPopFitness[x][7])
SecondGen[track2].append(CurrentPopFitness[x][8])
SecondGen[track2].append(CurrentPopFitness[x][9])
SecondGen[track2].append(CurrentPopFitness[x][10])
SecondGen[track2].append(CurrentPopFitness[x][11])
track2 += 1
Mutate = SecondGen
return Mutate
TruFal = True
while TruFal != False:
stop = input("Type X to stop the Genetic Algorithm, otherwise press enter")
if stop == "X":
TruFal = False
Mutate = OffspringMutation(SecondGen,Mutate)
also a cleaner method with the same logic as the last loop would be:
while True:
stop = input("Type X to stop the Genetic Algorithm, otherwise press enter")
Mutate = OffspringMutation(SecondGen,Mutate)
if stop == "X":
break

tkinter listbox: adding lines to listbox 1-by-1 through a function

I have a program that takes user input in the form of an integer, lets call it k. Three other numbers are known, a,b and c. My task is to find all positive integer solutions {x,y,z} such that
ax + by + cz = k. I wrote a method that I call on objects that have a,b and c built in. There is an additional constraint that says that x + y + z cannot be greater than a known integer p.
def find_all_solutions(self, k):
for x in range(0, k/c +1):
for y in range(0, k/c +1):
for z in range(0,k/c +1):
if x+y+z <= self.p and self.a*x+self.b*y+self.c*z == k:
one_solution = [x,y,z]
list_of_combinations.insert(END,"x: {0}, y: {1}, z: {2} ".format(one_solution[0], one_solution[1], one_solution[2]))
K = IntVar()
KassaBox= Entry(TeaterGUI, relief=GROOVE,textvariable=Kassa,width="15")
KassaBox.place(x="400",y="240")
KombinationsKnapp = Button(TeaterGUI, text="Tryck har for att visa alla mojliga kombinationer", command= lambda: TeaterLista[Teater_Index.get()].find_all_solutions(K.get()))
KombinationsKnapp.place(x="400",y="260")
This works when k is somewhat small (<100000), although as it exceeds 3 digits the interpreter freezes for a couple of seconds as it does its calculations, but eventually it does what its supposed to.
My problem is that if k is larger, the amount of combinations that have to be checked becomes too many to handle for the python interpreter.
So I was thinking that maybe a way of avoiding these crashes is to, instead of the program finding all solutions and adding them all at once, to make the program find each solution and add it one-by-one to the listbox, so as to avoid the computer storing to much information in its RAM before its used. However, with tkinters .insert method seeming like the only way of appending the listbox with info, I don't know how to do this.
Any help would be greatly appreciated!
instead of this line:
list_of_combinations.insert(END,"x: {0}, y: {1}, z: {2} ".format(one_solution[0], one_solution[1], one_solution[2]))
you could do:
yield "x: {0}, y: {1}, z: {2} ".format(one_solution[0], one_solution[1], one_solution[2])
then to add to the listbox you could do a simple iterative loop:
for solution in find_all_solutions(k):
listbox.insert("end", solution)
this will get your generator function to yield one answer at a time and allow you to add them one after another to the listbox.
however as the program never reaches the mainloop while this is taking place, the window is never updated. you could manually call window.update() between every insert, but this would slow down your computation.
some better options would be:
1) before you enter this computation loop, add a function like this:
def myupdate():
window.update()
window.after(100, myupdate)
and then just before you start the loop add a line like:
window.after(100, myupdate)
you will effectively start a threaded update loop, where the window would update roughly every 100ms
2) add a separate subloop to update the window after every so many inserts
eg:
a = 0
for solution in find_all_solutions(k):
listbox.insert("end", solution)
a += 1
if a == 10: #would update every 10 inserts
window.update()
a = 0

Python: Change condition value of while loop from called module inside loop

I want the variable for the condition of my while loop (while repeat is True) to be changed (to repeat = False, thus not meeting the condition of the while loop) via the if statement in the action() module that is being called within the while loop itself.
Comments should explain my intentions throughout.
Note This is a simplified version of the larger code I am actually working. Hopefully I made it simple and clear enough to get my point across without additional confusing code as I have come across in other posts.
# Defining the variables
repeat = True
class monster:
hp = 5
class fighter:
damage = 1
# Defining my action module
def action():
monster.hp -= fighter.damage # Monster's hp decreases by fighter's damage
print "Monster HP is %s" % monster.hp # Print this result
if monster.hp < 1: # Check to see if monster is dead, hp less than 1
repeat = False # If monster is dead, stop repeating
else:
repeat = True # If monster is not dead, repeat attack
# Here is the while loop
while repeat is True: # Defining the condition for the while loop
print "repeat is %r" % repeat # Here it should print repeat is True
action() # Then call the action module
print "repeat is %r" % repeat # Here it should print repeat is False
You have to declare repeat as a global variable to change it from inside action(). Include this line after the def action():
def action():
global repeat

Returning values from inside a while loop in python

I don't know if this is a simple question or impossible or anything, but I couldn't find anything on it so I figured I would ask it.
Is it possible to return values from a while loop while that loop is still running? Basically what I want to do is have a vector constantly updating within a while loop, but able to return values when asked without stopping the while loop. Is this possible? Do I just have to break up the program and put the while loop in a separate thread, or can I do it within one function?
Also I would prefer a method that is not computationally intensive (obviously), and one compatible with a rate-limited while loop as this one will certainly be rate-limited.
Again, if this is a stupid question just tell me and I will delete it, but I wasn't able to find documentation on this.
Code I am trying to implement this with:
def update(self, x_motion, y_motion, z_motion):
self.x_pos += self.x_veloc
self.y_pos += self.y_veloc
self.z_pos += self.z_veloc
self.x_veloc += self.x_accel
self.y_veloc += self.y_accel
self.z_veloc += self.z_accel
self.x_accel = x_motion[2]
self.y_accel = y_motion[2]
self.z_accel = z_motion[2]
while True:
self.update(x_motion, y_motion, z_motion)
print vector.x_accel
Something along those lines at least. It is important that these return outside of the while loop, so that the while loop runs in the background, but it only gives results when asked, or something like that.
Create a generator instead.
def triangle():
res = 0
inc = 1
while True:
res += inc
inc += 1
yield res
t = triangle()
print next(t)
print next(t)
print next(t)
print next(t)
EDIT:
Or perhaps a coroutine.
def summer():
res = 0
inc = 0
while True:
res += inc
inc = (yield res)
s = summer()
print s.send(None)
print s.send(3)
print s.send(5)
print s.send(2)
print s.send(4)
What you are looking for is yield:
def func():
while True:
yield "hello"
for x in func():
print(x)
Generators can also be written like list comprehensions:
Have a look at this question:
What does the "yield" keyword do?
If I understand you and the code you want to do.
I may think of trying to find equations of position, velocity and acceleration with respect to time.
So you won't need to keep, the while loop running, what you need is to convert it to a function, and using the time difference between the 2 function calls, you can perform the calculations you want, only when you call the function, rather than having the while loop running all the time.

Categories

Resources