How generators can work inside event loops like pyinput - python-3.x

For example, this code is not working:
from pynput import keyboard
def on_press(key):
global play
if str(key) == "'x'":
play = 'Play'
play = ''
with keyboard.Listener(on_press = on_press, suppress=True) as listener:
while listener.is_alive(): # infinite loop which runs all time
def pump():
num = 0
while True:
yield num # generator
num += 1
if play == 'Play':
next(gen)
listener.join()
No matter how I put the generator function or using a global variable, I always have a StopIteration: error or others.
I want to control the generator, so I can "play" and "pause" with keyboard keys.
While this code works:
def pump():
num = 0
while True:
yield num
num += 1
gen = pump()
next(gen) #play
next(gen)
I'm so bewildered...

I don't use Jupyter notebooks but I tried the VS Code extension and it seems to work in them as well.
Here is some sample code to prove the generator is working.
from pynput import keyboard
import sys
def on_press(key):
global play
if str(key) == "'x'":
play = 'Play'
# added to provide a way of escaping the while loop
elif str(key) == "'q'":
sys.exit(1)
def pump():
num = 0
while True:
# added as proof your generator is working
print(num)
yield num
num += 1
gen = pump()
play = ''
with keyboard.Listener(on_press=on_press, suppress=True) as listener:
while listener.is_alive():
if play == 'Play':
next(gen)
listener.join()
This will infinitely call next(gen) since there is no way to unset play from "Play" in the code. Once it's set, it will continue infinitely looping and if play == 'Play': will always be True.
Now, onto your example.
This line:
next(gen)
will error.
gen is never defined in your code.
I assume this was just a typo and you have gen = pump() somewhere.
Additionally, defining pump in your loop will almost certainly have unintended consequences.
Suppose you do have gen = pump() immediately after the definition.
play will be set to "Play" when you press the x key. You will continuously call next(gen).
However, this time after each loop pump will be redefined and num will be set to 0. It will yield the 0 and continue to do so infinitely.
So what did you mean to do?
Probably something like this:
from pynput import keyboard
import sys
def on_press(key):
global play
if str(key) == "'x'":
play = 'Play'
elif str(key) == "'q'":
sys.exit(1)
def pump():
global play
num = 0
while True:
play = ''
print(num)
yield num
num += 1
gen = pump()
play = ''
with keyboard.Listener(on_press = on_press, suppress=True) as listener:
while listener.is_alive():
if play == 'Play':
next(gen)
listener.join()
Personally, I would avoid the use of globals.
However this, calls the next(gen) once with the x key press. The generator sets play back to "" avoiding subsequent calls of next(gen) until x is pressed again.

Related

How to run time.sleep() without stopping while loop

Is there a way to call a function which has wait(time.sleep()) from infinite while loop without disturbing the loop?
I am trying to run a few task that require to wait for a few seconds but the issue is that the while loop also stops when the wait process is happening.
This is what I have tried out-
Here is my code:
import cv2
import time
def waiting():
print("Inside function")
# Running Some Tasks
time.sleep(5)
print("Done sleeping")
def main():
cap = cv2.VideoCapture(0)
while True:
ret, frame = cap.read()
cv2.imshow("Webcam", frame)
k = cv2.waitKey(10)
if k == 32: # Press SPACEBAR for wait function
waiting()
elif k == 27: # Press ESC to stop code
break
cap.release()
cv2.destroyAllWindows()
if __name__ == "__main__":
main()
Thanks for the quick responses from #JLT and #TerePiim. Here is the updated code for anyone who might benefit from this:
import cv2
import time
import threading
def waiting():
print("Inside parallel function")
# Running some Tasks
time.sleep(5)
print("Done sleeping")
def main():
cap = cv2.VideoCapture(0)
while True:
ret, frame = cap.read()
cv2.imshow("Webcam", frame)
k = cv2.waitKey(10)
if k == 32: # Press SPACEBAR for wait function
t = threading.Thread(target=waiting)
t.start()
elif k == 27: # Press ESC to stop code
break
cap.release()
cv2.destroyAllWindows()
if __name__ == "__main__":
main()
You are working in a single thread script at the moment. You should use threading or multiprocessing. This makes (it look like) multiple processes (are) active. Dependent on if you use threading or multiprocessing.
You should use threads. It will look like the computer is doing them both at the same time.
import threading
t = threading.Thread(target=function)
t.start()

Multi - threading click macro / click recorder

I am working on a script that will listen to keystrokes till the 'q' button is pressed, afterwards it should stop the script and print out the mouse positions that were saved in 2 seconds intervals. I can't manage the threads and I am still learning this topic. Each time I run the code nothing happens but the process is running:
from pynput.keyboard import Listener
import pyautogui
from multiprocessing import Process
import time
mouse_positions = []
def func1():
while True:
time.sleep(2)
mouse_positions.append(pyautogui.position())
cordinates = []
quit_status = False
keystrokes = []
def on_press(key):
if "q" in str(key) :
print('q was pressed!')
exit("Stopped running")
#qprint(key)
keystrokes.append(key)
print(keystrokes)
#print(keystrokes)
if __name__ == '__main__':
p1 = Process(target=func1)
p1.start()
p1.join()
with Listener(on_press=on_press) as listener: # Create an instance of Listener
listener.join() # Join the listener thread to the main thread to keep waiting for keys
EDIT :
To anyone intrested, here is a click macro I built, script I built previously was more like mouse capture movement. The script below will record your mouse clicks and afterwards will replay them. Much better.
from pynput.keyboard import Listener
import pyautogui
from pynput import mouse
import time
x_pos = []
y_pos = []
both_pos = []
pressed_key = None
def on_click(x, y, button, pressed):
if pressed:
#print ("{0} {1}".format(x,y))
print(pressed_key)
if pressed_key == "1":
both_pos.append("{0}".format(x,y))
both_pos.append("{1}".format(x,y))
#print("test" + x_pos + y_pos)
print (x_pos + y_pos)
else:
pass
if pressed_key == 'q':
return False
def on_press(key):
print("To replay press 'q' , to stop recording press '1' , to record again press '1' .")
global pressed_key
if 'Key.esc' in str(key):
return False
if '1' in str(key):
pressed_key= None if pressed_key == '1' else '1'
if 'q' in str(key):
print("Replaying actions")
print(str(len(both_pos)))
for point in range(0,len(both_pos),2):
time.sleep(3)
print("clicking")
pyautogui.click(x=int(both_pos[point]),y=int(both_pos[point+1]))
print("done...")
return False
mouse_listener = mouse.Listener(on_click=on_click)
mouse_listener.start()
with Listener(on_press=on_press) as listener: # Create an instance of Listener
listener.join()
#print(mouse_listener.mouse_positions)
Hi you can use threading module.
I have created class MouseListener which inherit from threading.Thread class. Everything what you want to run put into run method. As thread stopper I used still_run attribute.
When you are typing, I pass to on_press function pressed key and mouse_listener. If q is pressed I set mouse_listener.still_run to False, what leads to stop the mouse listener.
mouse_positions I moved from global scope to MouseListener.
import threading
from pynput.keyboard import Listener
import pyautogui
import time
class MouseListener(threading.Thread):
still_run = True
mouse_positions = []
def run(self):
self.func()
def func(self):
while self.still_run:
time.sleep(2)
self.mouse_positions.append(pyautogui.position())
print(self.mouse_positions)
coordinates = []
quit_status = False
keystrokes = []
def on_press(key, mouse_listener):
print('kp')
if "q" in str(key):
print('q was pressed!')
mouse_listener.still_run = False
print(key)
exit("Stopped running")
keystrokes.append(key)
print(keystrokes)
print(keystrokes)
if __name__ == '__main__':
mouse_listener = MouseListener()
mouse_listener.start()
with Listener(on_press=lambda key: on_press(key, mouse_listener)) as listener: # Create an instance of Listener
listener.join()
print(mouse_listener.mouse_positions)

How to use event.value for another function outside of the main loop? [duplicate]

This question already has answers here:
How to run multiple while loops at a time in Pygame
(1 answer)
Spawning multiple instances of the same object concurrently in python
(1 answer)
Closed 1 year ago.
Below is a working skeleton of my actual code. What I am attempting to do is to obtain the JOYAXISMOTION, event.value, y so that I can use it in different function outside of the loop. I have already defined the variable y.
import time
import sys
import pygame
import subprocess
import random
from pygame.locals import *
pygame.init()
y = 0
def get_percent_change(current, previous):
if current == previous:
return 0
try:
return ((float(current) - float(previous)) /abs(previous)) * 100.0
except ZeroDivisionError:
return float('inf')
def sendCommands():
time.sleep(5)
print('Hello World')
pygame.joystick.init()
joysticks = [pygame.joystick.Joystick(i) for i in range(pygame.joystick.get_count())]
for joystick in joysticks:
print(joystick.get_name())
game_state = False
run = True
while run:
for event in pygame.event.get():
if event.type == JOYBUTTONDOWN:
print(event)
if game_state == False and event.button == 8:
game_state = True
if event.button == 4:
game_state = False
if event.type == JOYHATMOTION:
if event.value[0] == 1 or event.value[0] == -1:
game_state = False
if event.type == JOYAXISMOTION:
if event.axis == 4:
current = event.value
y = get_percent_change(current, -1.0001)
print(y)
if event.type == JOYDEVICEADDED:
joysticks = [pygame.joystick.Joystick(i) for i in range(pygame.joystick.get_count())]
for joystick in joysticks:
print(joystick.get_name())
if event.type == JOYDEVICEREMOVED:
joysticks = [pygame.joystick.Joystick(i) for i in range(pygame.joystick.get_count())]
if game_state == True:
sendCommands()
pygame.quit()
sys.exit()
The main problem I am running into is the time.sleep(5) that the sendCommands requires. It is blocking the script while sleeping. I have tried asyncio with no success. Threading is also very confusing to me here. I have also tried the delay logic in the answer provided to a closely related question here but that also blocks the code from running.
Allow me to pick your brain on how:
I can run the script without it being blocked
and how I can access and use the event.value, y outside of the loop
To use y outside of the loop, you could declare it as global but a better alternative is to make a function to get the value. You can implement a timer to execute your subprocess every 5 seconds instead of blocking the entire thread. The following code prints None every 5 seconds for me since I don't have any controllers connected but I think it will work for you.
import time
import sys
import pygame
from pygame.locals import *
pygame.init()
def sendCommands(waitTime, y):
global sendCommandsStart
#count how many seconds have passed since sendCommandsStart
#variable was last updated
if time.time() - sendCommandsStart > waitTime:
sendCommandsStart = time.time() #reset the timer
print(y)#call your subpreocess here
def getJoyStickY(events):
for event in events:
if event.type == JOYAXISMOTION:
if event.axis == 4:
return get_percent_change(current, -1.0001)
return None
pygame.joystick.init()
run = True
sendCommandsStart = time.time() #get the current time - its just a number (time since last epoh)
while run:
allEvents = pygame.event.get()
sendCommands(5, getJoyStickY(allEvents))
pygame.quit()
sys.exit()

How to make the global value reset each time?

def playAgain():
b = input('Do you want to play again? y/n')
if b == ('y'):
def startGame():
startGame()
else:
print('Goodbye!')
time.sleep(1)
sys.exit()
import random
import time
import sys
global shots
shots = 0
while shots <=5:
chanceofDeath =random.randint(1,6)
input('press enter to play Russian roulette.')
if chanceofDeath ==1:
shots = shots + 1
print (shots)
print('You died.')
time.sleep(1)
playAgain()
else:
shots = shots + 1
print (shots)
print ('click')
if shots == 5:
print('You won without dying!')
time.sleep(1)
playAgain()
When I run the program, when it asks to play again or not, if you choose yes it works, but continues from the last shot. For example, if you died on the second shot and played again, instead of restarting, it starts right off at 3. How do I make the shots reset everytime?
The reason why it continues from the last shot is because you never actually set the 'shots' back to 0 this code:
import random
import time
import sys
global shots
shots = 0
is ran only once, meaning shots is never assigned back to 0.
What you want is if the user chooses to play again, the 'shots' variable should be set back to 0. You could edit your playAgain() function to return True if the user wanted to play again. For example:
def playAgain():
b = input('Do you want to play again? y/n')
if b == ('y'):
return True
else:
print('Goodbye!')
time.sleep(1)
sys.exit()
This allows you to check for whether the user wanted to play again in your main while loop and set 'shots' to 0 like this:
if playAgain():
shots = 0
Also as shots is declared outside of any functions and the while loop is the only thing using it, it does not need to be defined as a global variable.
Revised Program
def playAgain():
b = input('Do you want to play again? y/n')
if b == ('y'):
return True
else:
print('Goodbye!')
time.sleep(1)
sys.exit()
import random
import time
import sys
shots = 0
while shots <=5:
chanceofDeath =random.randint(1,6)
input('press enter to play Russian roulette.')
if chanceofDeath ==1:
shots = shots + 1
print (shots)
print('You died.')
time.sleep(1)
if playAgain():
shots = 0
else:
shots = shots + 1
print (shots)
print ('click')
if shots == 5:
print('You won without dying!')
time.sleep(1)
if playAgain():
shots = 0
Also I am unsure of what you wanted your code to do with the following:
def startGame():
startGame()
Hope this helps

Pyaudio setting output = to variable

How could you set a variable equal to 'O' or '-' and then put that in an if statement like the one below:
if variable == 'O':
print 'hi'
how could you do that for:
import threading
from array import array
from Queue import Queue, Full
import pyaudio
CHUNK_SIZE = 1024
MIN_VOLUME = 500
BUF_MAX_SIZE = CHUNK_SIZE * 10
def main():
stopped = threading.Event()
q = Queue(maxsize=int(round(BUF_MAX_SIZE / CHUNK_SIZE)))
listen_t = threading.Thread(target=listen, args=(stopped, q))
listen_t.start()
record_t = threading.Thread(target=record, args=(stopped, q))
record_t.start()
try:
while True:
listen_t.join(0.1)
record_t.join(0.1)
except KeyboardInterrupt:
stopped.set()
listen_t.join()
record_t.join()
def record(stopped, q):
while True:
if stopped.wait(timeout=0):
break
chunk = q.get()
vol = max(chunk)
if vol >= MIN_VOLUME:
# TODO: write to file
print "O",
else:
print "-",
def listen(stopped, q):
stream = pyaudio.PyAudio().open(
format=pyaudio.paInt16,
channels=2,
rate=44100,
input=True,
frames_per_buffer=1024,
)
while True:
if stopped.wait(timeout=0):
break
try:
q.put(array('h', stream.read(CHUNK_SIZE)))
except Full:
pass # discard
if __name__ == '__main__':
main()
Could you use that so if the output is 'O' then print hi? Will somebody write the code for me because I have been trying for a little bit to write this code and I have still not been able to make the code work for me. Thank You.
In order to use if then statement in Python, first we need to declare variable value with proper syntax according to requirement and type.
s = "O"
if s == 'O':
print 'hi'

Resources