python中的键盘输入(通过pynput)和线程

python中的键盘输入(通过pynput)和线程,python,multithreading,input,Python,Multithreading,Input,我正在尝试用Python 3制作一种基于文本的游戏。在游戏中,我需要聆听键盘输入,特别是在屏幕上打印内容时,测量按键按下的时间。我试着从一个简单的例子开始 首先,以下代码使用pynput,成功地测量了用户按下键的时间长度: from pynput import keyboard import time print("Press and hold any key to measure duration of keypress. Esc ends program") # A

我正在尝试用Python 3制作一种基于文本的游戏。在游戏中,我需要聆听键盘输入,特别是在屏幕上打印内容时,测量按键按下的时间。我试着从一个简单的例子开始

首先,以下代码使用
pynput
,成功地测量了用户按下键的时间长度:

from pynput import keyboard 
import time

print("Press and hold any key to measure duration of keypress. Esc ends program")

# A dictionary of keys pressed down right now and the time each was pressed down at
keys_currently_pressed = {} 

def on_press(key):
    global keys_currently_pressed 
    # Record the key and the time it was pressed only if we don't already have it
    if key not in keys_currently_pressed:
        keys_currently_pressed[key] = time.time()

def on_release(key):
    global keys_currently_pressed
    if key in keys_currently_pressed:
        animate = False
        duration = time.time() - keys_currently_pressed[key]
        print("The key",key," was pressed for",str(duration)[0:5],"seconds")
        del keys_currently_pressed[key]
    if key == keyboard.Key.esc:
        # Stop the listener
        return False

with keyboard.Listener(on_press = on_press, on_release=on_release, suppress=True) as listener: 
    listener.join()
现在我想做的是,只有当用户按下一个键时,才能在屏幕上打印一个基于文本的“动画”。在下面的示例中,我的“动画”只是每半秒打印一次
“*”
。 到目前为止,我已经尝试让第二个线程处理“动画”,但对于多线程,我完全是个新手。以下代码将在正确的时间启动动画,但不会停止动画

from pynput import keyboard 
import sys
import time
import threading

print("Press and hold any key to measure duration of keypress. Esc ends program")

# A dictionary of keys pressed down right now and the time each was pressed down at
keys_currently_pressed = {} 

def my_animation():
    # A simple "animation" that prints a new "*" every half second
    limit = 60 # just in case, don't do more than this many iterations
    j = 0
    while j<limit:
        j += 1
        sys.stdout.write("*")
        time.sleep(0.5)

anim = threading.Thread(target=my_animation)

def on_press(key):
    global keys_currently_pressed 
    # Record the key and the time it was pressed only if we don't already have it
    if key not in keys_currently_pressed:
        keys_currently_pressed[key] = time.time()
        anim.start()

def on_release(key):
    global keys_currently_pressed
    if key in keys_currently_pressed:
        animate = False
        duration = time.time() - keys_currently_pressed[key]
        print("The key",key," was pressed for",str(duration)[0:5],"seconds")
        del keys_currently_pressed[key]
    if key == keyboard.Key.esc:
        # Stop the listener
        return False

with keyboard.Listener(on_press = on_press, on_release=on_release, suppress=True) as listener:         listener.join()
最终,我希望能够用更复杂的动画来实现这一点。比如说

def mysquare(delay):
    print("@"*10)
    time.sleep(delay)
    for i in range(8):
        print("@" + " "*8 + "@")
        time.sleep(delay)
    print("@"*10)

正确的方法是什么?非常感谢

监听器
已经使用了
线程
,因此不需要在单独的线程中运行动画。您可以在当前胎面花纹中运行它

with keyboard.Listener(on_press = on_press, on_release=on_release, suppress=True) as listener:         

    #... your code ...

    listener.join()
或者不使用
而使用。。。作为…

listener = keyboard.Listener(on_press = on_press, on_release=on_release, suppress=True)
listener.start()

#... your code ...

#listener.wait()
listener.join()
您甚至可以在那里运行长时间运行的代码,即无休止的
while
循环,该循环将检查变量
animate
是否为
True
,并编写新的
*

我必须在Linux上添加
sys.stdout.flush()
,才能在屏幕上看到
*


我的版本:

当您按下任何按钮时,它会一直运行动画,但也有带有变量
counter
的代码将动画限制为6个移动。如果在运行动画时按“新建”键,则会重置此计数器,动画将变长

此循环必须一直运行,以检查是否有新动画-动画完成后无法完成此循环

from pynput import keyboard 
import sys
import time

# --- functions ---

def on_press(key):
    global keys_currently_pressed 
    global animate
    #global counter

    # Record the key and the time it was pressed only if we don't already have it
    if key not in keys_currently_pressed and key != keyboard.Key.esc:
        keys_currently_pressed[key] = time.time()
        animate = True
        #counter = 0 # reset counter on new key

def on_release(key):
    global keys_currently_pressed
    global animate

    if key in keys_currently_pressed:
        duration = time.time() - keys_currently_pressed[key]
        print("The key", key, "was pressed for", str(duration)[0:5], "seconds")
        del keys_currently_pressed[key]

        if not keys_currently_pressed: 
            animate = False

    if key == keyboard.Key.esc:
        # Stop the listener
        return False

# --- main ---

print("Press and hold any key to measure duration of keypress. Esc ends program")

# A dictionary of keys pressed down right now and the time each was pressed down at
keys_currently_pressed = {} 

animate = False # default value at start (to use in `while` loop)
#limit = 6 # limit animation to 6 moves
#counter = 0 # count animation moves

with keyboard.Listener(on_press = on_press, on_release=on_release, suppress=True) as listener:         

    while listener.is_alive(): # infinite loop which runs all time

        if animate:
            #sys.stdout.write("\b *")  # animation with removing previous `*` 
            sys.stdout.write("*")  # normal animation
            sys.stdout.flush()  # send buffer on screen
            #counter += 1
            #if counter >= limit:
            #    counter = 0
            #    animate = False
            
        time.sleep(0.5)


    listener.join()

listener
已在单独的线程中运行,因此无需使用其他线程进行动画。您可以在
与之间的当前线程中运行代码。。。作为监听器
listener.join()
——它可以是长时间运行的循环,用于检查变量和更新屏幕。它的工作原理与PyGame类似,PyGame也会运行循环,始终检查是否有东西需要移动和重画elements@furas谢谢你的提示,我已经试着按照你的建议去做了,现在在最新的问题编辑中显示出来了——但我似乎无法让它起作用。谢谢你,这非常有帮助,这对我很有用。但是假设动画比一遍又一遍地打印同一个角色更复杂,就像假设它是通过ascii艺术绘制某种形状一样。我编辑了这个问题,以得到一个类似这样的例子。这种方法能适应这样做吗?如果你需要更复杂的动画,那么你将需要在
while loop
中使用更复杂的代码。同样,这个动画应该在每个循环中只画一帧-不要运行内部循环。如果你想用sleep绘制
mysquare
,那么这个想法是错误的-你应该只有一个循环-`while listener.is_alive()`-它应该控制时间并绘制新元素。好的,谢谢你的建议。我想我可以用一种计数器跟踪动画状态,在开始一个新动画时,计数器会被重置。我接受这个答案。
from pynput import keyboard 
import sys
import time

# --- functions ---

def on_press(key):
    global keys_currently_pressed 
    global animate
    #global counter

    # Record the key and the time it was pressed only if we don't already have it
    if key not in keys_currently_pressed and key != keyboard.Key.esc:
        keys_currently_pressed[key] = time.time()
        animate = True
        #counter = 0 # reset counter on new key

def on_release(key):
    global keys_currently_pressed
    global animate

    if key in keys_currently_pressed:
        duration = time.time() - keys_currently_pressed[key]
        print("The key", key, "was pressed for", str(duration)[0:5], "seconds")
        del keys_currently_pressed[key]

        if not keys_currently_pressed: 
            animate = False

    if key == keyboard.Key.esc:
        # Stop the listener
        return False

# --- main ---

print("Press and hold any key to measure duration of keypress. Esc ends program")

# A dictionary of keys pressed down right now and the time each was pressed down at
keys_currently_pressed = {} 

animate = False # default value at start (to use in `while` loop)
#limit = 6 # limit animation to 6 moves
#counter = 0 # count animation moves

with keyboard.Listener(on_press = on_press, on_release=on_release, suppress=True) as listener:         

    while listener.is_alive(): # infinite loop which runs all time

        if animate:
            #sys.stdout.write("\b *")  # animation with removing previous `*` 
            sys.stdout.write("*")  # normal animation
            sys.stdout.flush()  # send buffer on screen
            #counter += 1
            #if counter >= limit:
            #    counter = 0
            #    animate = False
            
        time.sleep(0.5)


    listener.join()