首页 > 解决方案 > 键盘输入(通过pynput)和python中的线程

问题描述

我正在尝试在 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()

这是一种方法(遵循@furas 的评论),其中动画是在with语句之​​后编码的,但是我无法让它为我工作:

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 = {} 

# animation flag
anim_allowed = False

def on_press(key):
    global keys_currently_pressed 
    global anim_allowed
    # 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_allowed = True

def on_release(key):
    global keys_currently_pressed
    global anim_allowed
    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]
        anim_allowed = False
    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:
    while anim_allowed:
        sys.stdout.write("*")
        time.sleep(0.5)
    listener.join()

最终,我希望能够使用更复杂的动画来做到这一点。例如

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

解决这个问题的正确方法是什么?非常感谢!

标签: pythonmultithreadinginput

解决方案


Listener已经使用thread,因此无需在单独的线程中运行动画。您可以在当前踏入中运行它

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

    #... your code ...

    listener.join()

或没有with ... as ...

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

#... your code ...

#listener.wait()
listener.join()

您甚至可以在那里运行长时间运行的代码 - 即。无限while循环,它将检查变量animate是否为True并写入 new *

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


我的版本:

当您按下任何按钮时,它会一直运行动画,但也有带有变量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()

推荐阅读