How do I exit a while loop in python with an event? - python

I'm making this project for school that involves displaying data to a raspberry pi. The code I'm using refreshes (and needs to refresh) incredibly quickly, but I need a way for the user to stop the output, which I believe requires some kind of key event. The thing is, I'm new to Python and I can't figure out how to exit a while loop with turtle.onkey(). I found this code:
import turtle
def quit():
global more
more = False
turtle.onkey(quit, "Up")
turtle.listen()
more = True
while more:
print("something")
Which doesn't work. I've tested it. How do I make this work, or is there another way to get user input without interrupting the flow of the program?

while loop run on thread
check this code
import threading
def something():
while more:
print("something")
th = threading.Thread(something)
th.start()

Avoid infinite loops in a Python turtle graphics program:
more = True
while more:
print("something")
You can effectively block events from firing, including the one intended to stop the loop. Instead, use timer events to run your code and allow other events to fire:
from turtle import Screen
more = True
counter = 0
def stop():
global more
more = False
def start():
global more
more = True
screen.ontimer(do_something, 100)
def do_something():
global counter
print("something", counter)
counter += 1
if more:
screen.ontimer(do_something, 100)
screen = Screen()
screen.onkey(stop, "Up")
screen.onkey(start, "Down")
screen.listen()
start()
screen.mainloop()
I've added a counter to your program just so you can more easily see when the 'something' statements stop and I've added a restart on the down key so you can start them up again. Control should always reach mainloop() (or done() or exitonclick()) to give all the event handlers a chance to execute. Some infinite loops allow events to fire but they typically have calls into the turtle methods that allow it to have control some of the time but are still the wrong approach.

Chances are that you are trying to run your code in an interactive IPython shell. That does not work. The bare Python repl shell works, though.
Here I found a project that tries to bring turtle to IPython: https://github.com/Andrewkind/Turtle-Ipython. I did not test it, and I'm not exactly sure that this is a better solution than simply using the unsugared shell.

You could have you loop check a file like this:
def check_for_value_in_file():
with open('file.txt') as f:
value = f.read()
return value
while check_for_value_in_file() == 'the right value':
do_stuff()

Related

A function is running despite me changing a variable that should stop it

I was hoping someone could help me with this issue. I'm hoping it's fairly simple to fix, but I have been trying for a while to figure this out. I have trimmed my larger code to this, as I believe the issue here is the crux of the problem.
I have a raspberry pi and an external button. This is on python3 on Linux. I am using GPIOZero for the button. The code below I believe is simple to understand, but basically, I want a function to loop at all times. When I press a button I want another function to run, but only if a variable is a certain number. I describe what I ultimately want to happen in a comment below, but my code is unfinished and simplified just for this problem.
I only want button.when_pressed to work when timer = 0. The problem is, once the code naturally gets into the button.when_pressed function, it never "lets go" of the function again. When I successfully redefine the variable to timer = 1, it still prints button is pressed when I press the button. I don't know why. To me, it seems like it should only work once timer = 0.
Any suggestions? I think I have a misunderstanding of global variables I will plan on researching. I'm not sure if that's the issue. I have also tried using break and continue to try to get it "back on its loop" but that hasn't worked either. Also I want to use button.when_pressed instead of btn.is_pressed, because I want the program to only do something when I'm holding the button down once, and not loop when I'm holding it down. In this case, I want button is pressed to print a single time. If I did btn.is_pressed it would print button is pressed every two seconds, which I dont want.
Thanks for any help. I'm happy to learn.
#!/usr/bin/python3
from gpiozero import Button
from time import sleep
import time
button = Button(4)
timer = 0
def press():
print("Button is pressed")
global timer
timer = 1
def loop():
global timer
timer = 1
while True:
if timer == 0:
button.when_pressed = press
else:
loop()
sleep(2)
If you want to disable the callback you had set to button.when_pressed, you need to do another assignment with button.when_pressed = None. This is listed in the documentation:
when_released:
[...] Set this property to None (the default) to disable the event.
It's not exactly clear what behavior you want from your current code. If you want the button to be active for 2 seconds, then be deactivated indefinitely, you can use:
button.when_pressed = press
sleep(2)
button.when_pressed = None
There's no need for a loop, since you don't want to repeat anything.
If you only want the button to be active for a single button press, that needs to happen within 2 seconds, you could instead call button.wait_for_press(2). I hesitate to write a full block of code for that though, as the docs don't specify how a timeout is signaled (it might be by return value, or via an exception). I don't have a Raspberry Pi so I can't test myself, but you could try it out and see what happens.
Treat your whole piece of code as one "black box", ask yourself, what is the input/output? button press or timer mode? (because I don't quite understand what does timer variable mean in your code)
Your code implies timer mode is the top level input to control the flow,
while True:
if timer == 0:
button.when_pressed = press
else:
loop()
sleep(2)
Is it expected?
If you allow user to press the button at any time, suggest you make button press to be your top level input, change the logic to keep when_pressed callback always on, set flag once triggered, and then check if the button has been pressed and still is_pressed in your while loop.
pressed = False
def play_video_1():
pass
def play_video_2():
pass
def press():
print("Button is pressed")
global pressed
pressed = True
button.when_pressed = press
while True:
if pressed and not_playing_video2:
if is_pressed:
play_video_1()
else:
pressed = False
play_video_2()
else:
play_video_2()

python can't break while loop when play audio with pygame-change

I tried something new to fit the code. Now when I press ctrl + s the audio really stops but when my personal assistant plays music again in the same run, the music does not stop as for the first time:
import glob
import os
import keyboard
os.environ['PYGAME_HIDE_SUPPORT_PROMPT'] = "hide"
import pygame
songs = glob.glob("C:\\Users\zivsi\Music\\*.mp3")
import random
song = random.choice(songs)
with open("song_choice.txt", "r") as file:
read = file.read()
if (read == song):
print("switch song")
song = random.choice(songs)
song_name = song.replace("C:\\Users\zivsi\Music\\", "").replace(".mp3", "")
print("song: ", song_name)
pygame.mixer.init()
music = pygame.mixer.music.load(song)
pygame.mixer.music.play()
while (pygame.mixer_music.get_busy()):
pygame.time.Clock().tick(10)
keyboard.add_hotkey("ctrl + s", lambda: pygame.mixer_music.stop())
# if i will press ctrl + s, the music will stop but it's working only once`
You can't use break in a different scope, it has to be part of the loop suite itself (so nested inside a while or for statement). A lambda is a function, which is a different scope.
Because you want to break out of the loop asynchronously, you'll need some other way to communicate from the keyboard callback handler back to the main loop. In general computer engineering terms, you need some kind of synchronization primitive to synchronize between different things that are happening at the same time, concurrently.
That is to say, you want to set some kind of flag, one that you can check in your while loop, so while pygame.mixer_music.get_busy() and not keyboard_ctrl_s_flag:. You can do this with any global value, and that'd probably be safe:
keyboard_ctrl_s_flag = False
def control_s_handler():
"""Called when the keyboard combination CTRL-S is pressed"""
global keyboard_ctrl_s_flag
keyboard_ctrl_s_flag = True
keyboard.add_hotkey("ctrl + s", control_s_handler)
then you can use that in your loop:
# reset the flag to false, in case it was already set to true before
keyboard_ctrl_s_flag = False
while pygame.mixer_music.get_busy() and not keyboard_ctrl_s_flag:
pygame.time.Clock().tick(10)
I say this probably is safe, because I'm not quite sure how PyGame is running its event handling loop. Given that you also used pygame.time.Clock().tick(10) in your loop makes me think that the event loop probably waits for your Python code to return control before doing other things.
The next time that the user then uses CTRL-S, the control_s_handler() callback is called, keyboard_ctrl_s_flag is set to True, and when control returns to the while loop (because pygame.time.Clock().tick(10) returned control to your Python code) the while loop exits even if pygame.mixer_music.get_busy() is still true.

Problem with using keyboard module and turtle together

I am writing a script to read my keystrokes and draw simple shapes in turtle.
To record keystrokes, I am using the keyboard module and I am using turtle for drawing.
I am getting struck due to the use of threading in the keyboard module.
What I am currently doing is-
I added hotkeys using keyboard.add_hotkey method.
If I am pressing a certain key, that letter is added to a list (named data) for later use.
When I press the combination of Ctrl+Shift+S, the save function is called. In the save function, a turtle window is instantiated, and the list data is popped one letter at a time. The shape is drawn according to the letter popped.
When the list gets empty, I save the drawing and close the turtle window.
The problem that I am facing is that once the save function is called, the program stops listening to other calls. It is perhaps due to the use of threads in keyboard module.
The code is attached here-
def start():
#turtle.mainloop()
s=turtle.Screen().setup( width = WIDTH, height = HEIGHT, startx = 0, starty = 0)
global t
t=turtle.Turtle()
turtle.ht()
t.ht()
def save():
start()
global t
global data
t.speed(0)
while data:
fun = data.pop()
if fun=='c':
draw_circle()
elif fun=='r':
draw_rectangle()
elif fun=='p':
draw_polygon()
elif fun=='h':
draw_hexagon()
elif fun=='t':
draw_triangle()
elif fun=='m':
draw_pentagon()
ts = turtle.getscreen()
ts.getcanvas().postscript(file="drawing.eps")
img = Image.open('drawing.eps')
img.save('drawing.png')
turtle.bye()
def push_fun(fun):
data.append(fun)
if __name__=='__main__':
keyboard.add_hotkey('ctrl+shift+s', save)
keyboard.add_hotkey('ctrl+shift+e', exit)
keyboard.add_hotkey('ctrl+shift+p', send_to_server)
# keyboard.add_hotkey('ctrl+shift+s', save, args=(data))
keyboard.add_hotkey('c', push_fun, args=('c',))
keyboard.add_hotkey('s', push_fun, args=('t',))
keyboard.add_hotkey('h', push_fun, args=('h',))
keyboard.add_hotkey('p', push_fun, args=('p',))
keyboard.add_hotkey('r', push_fun, args=('r',))
keyboard.wait()
After the save function is called, the program remains in the keyboard.wait() part but does not listen to any other key press.
you can end the wait by setting a key to press like:
keyboard.wait('space')
but keyboard.wait() without any keys given blocks all keystrokes forever
I'm not sure why you even put it there, I'm not even sure why it picks anything up at all, but I think you can do this:
if __name__=='__main__':
'''your keystrokes'''
while True: #or set a timer or something
pass
you didn't give me the whole code so I can't make sure it works
you also need to global the list data in the push_fun function
I hope it helps :)

how to add a def function with a while true loop

I've added a function definition to tell my turtle to jump when you press the space bar. There is also a while True loop in my code, and whenever the space button is pressed, the while True loop freezes momentarily until the jump is finished and then carries on.
I've tried adding the function definition in the while True loop and outside. I can only put the function definition before the while True loop, because if I put it after the while True, the code will never reach it.
#making the player jump
def jump():
player.fd(100)
player.rt(180)
player.fd(100)
player.lt(180)
turtle.listen()
turtle.onkey(jump, "space")
I'm expecting the while True loop to not freeze, but wherever I've tried putting the def, it doesn't seem to work.
I also saw another answer to something similar to this, but didn't understand how I could apply that to my code.
Any other suggestions would be great.
Until you get that async stuff to work, here's a minimalist implementation using turtle's own timer event to keep an obstacle moving forward, even when jumping:
from turtle import Screen, Turtle
def jump():
screen.onkey(None, 'space') # disable handler inside handler
if jumper.ycor() == 0: # if jumper isn't in the air
jumper.forward(150)
jumper.backward(150)
screen.onkey(jump, 'space')
def move():
hurdle.forward(6)
if hurdle.xcor() > -400:
screen.ontimer(move, 100)
jumper = Turtle('turtle')
jumper.speed('slowest')
jumper.penup()
jumper.setheading(90)
hurdle = Turtle('turtle')
hurdle.speed('fastest')
hurdle.penup()
hurdle.setheading(180)
hurdle.setx(400)
screen = Screen()
screen.onkey(jump, 'space')
screen.listen()
move()
screen.mainloop()
You can find several, more fleshed out versions of this on SO by searching for '[turtle-graphics] jump'
Avoid using a while True: loop in turtle's event-based environment.
I expected that it can be hard to do it with async but I built example which works.
Inside jump I use asyncio.sleep so this way when one turtle is sleeping then other turtle can walk. Without asyncio.sleep first turtle walk all the time.
import asyncio
import turtle
t1 = turtle.Turtle()
t2 = turtle.Turtle()
async def jump1():
while True:
t1.fd(100)
await asyncio.sleep(0.01)
t1.left(90)
async def jump2():
while True:
t2.fd(100)
await asyncio.sleep(0.01)
t2.right(90)
tasks = [jump1(), jump2()]
# Python 3.6.7
loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.wait(tasks))
# Python 3.7
#asyncio.run(asyncio.wait(tasks))
But building something more complex can be more difficult.
I also found aioturtle - turtle which use async. Maybe it will be easier to use aioturtle.
The problem is the following:
the code for the jump event is called synchronously. That means, everything has to wait for jump() to finish before continuing. this can maybe be fixed by declaring / calling the jump() method asynchronously. This website does a good job of explaining what asynchronous python means and how to implement it: https://hackernoon.com/asynchronous-python-45df84b82434
in short, here's how it would be implemented(in python 3.3 or higher):
async def jump():
#your code here
this just makes the jump() function run asynchronously.
now, whenever you call it, you have to call it like this:
await jump()
this might not work, depending on your exact setup.
i hope this helps.
please, ask me if you have any further questions.
EDIT: example

How do I get a variable from a running python function

I am working on some code that will count mouse clicks on a beaglebone board in python.
My question is how to structure code such that I can access the total number of mouse clicks while the mouse click counter function is still running (indefinately), and at the same time not interrupt the mouse click counter function ( i dont want to miss a click!)?
Is there a way to access variables in a running function in python without interrupting it?
Depends on how you're wanting to access them. An infinite loop works well.
_clicks = 0
while True:
if _clicks != clicks:
_clicks = clicks
print(_clicks)
But you'll almost certainly have to put that in another thread.
from threading import Thread
def notifier():
global clicks # whatever variable you're accessing
while True:
if _clicks != clicks:
_clicks = clicks
print(_clicks)
t = Thread(target=notifier)
t.start()

Categories