如何检测按键?

197
我正在用Python制作一个秒表类型的程序,我想知道如何检测按键是否被按下(例如按下p暂停,按下s停止),而且我不希望使用像raw_input这样的方法,因为它会在继续执行之前等待用户的输入。
有人知道如何在while循环中实现这个功能吗?
我希望能够在多个平台上运行,但如果不可能的话,我的主要开发目标是Linux。

1
适用于OS X的https://dev59.com/zGYs5IYBdhLWcg3wBvRp#47197390在Python 2和3中都有效。 - neoDev
17个回答

4

您没有提到这是一个图形用户界面程序还是命令行程序,但大多数GUI框架都包括一种捕获和处理键盘输入的方式。例如,使用tkinter(在Py3中),您可以绑定到特定事件,然后在函数中处理它。例如:

import tkinter as tk

def key_handler(event=None):
    if event and event.keysym in ('s', 'p'):
        'do something'

r = tk.Tk()
t = tk.Text()
t.pack()
r.bind('<Key>', key_handler)
r.mainloop()

以上方法,当您在文本窗口中输入文字时,key_handler例程会在您按下每个(或几乎每个)按键时被调用。


3
模块可以完成这项工作。

您可以从终端运行以下示例进行测试:

import curses

screen = curses.initscr()
curses.noecho()
curses.cbreak()
screen.keypad(True)

try:
    while True:
        char = screen.getch()
        if char == ord('q'):
            break
        elif char == curses.KEY_UP:
            print('up')
        elif char == curses.KEY_DOWN:
            print('down')
        elif char == curses.KEY_RIGHT:
            print('right')
        elif char == curses.KEY_LEFT:
            print('left')
        elif char == ord('s'):
            print('stop')

finally:
    curses.nocbreak(); screen.keypad(0); curses.echo()
    curses.endwin()

这个处理左箭头和上箭头同时按下的情况吗? - AwokeKnowing
这在Windows上不起作用。 - jpp1

3

使用keyboard包,特别是在Linux上,不是一个恰当的解决方案,因为该包需要root权限才能运行。我们可以很容易地使用getkey包来实现这一点。这类似于C语言函数getchar。

安装它:

pip install getkey

然后使用它:

from getkey import getkey
while True: #Breaks when key is pressed
    key = getkey()
    print(key) #Optionally prints out the key.
    break

我们可以将此添加到一个函数中,以返回所按下的键。
def Ginput(str):
    """
    Now, this function is like the native input() function. It can accept a prompt string, print it out, and when one key is pressed, it will return the key to the caller.
    """
    print(str, end='')
    while True:
        key = getkey()
        print(key)
        return key

使用方法如下:

inp = Ginput("\n Press any key to continue: ")
print("You pressed " + inp)

3
根据该项目显示的几个问题,getkey似乎不再得到积极维护,并且在Windows上进行pip安装存在问题。 - Marc L.

2

这里提供一种跨平台的解决方案,支持阻塞和非阻塞方式,并且不需要任何外部库:

import contextlib as _contextlib

try:
    import msvcrt as _msvcrt

    # Length 0 sequences, length 1 sequences...
    _ESCAPE_SEQUENCES = [frozenset(("\x00", "\xe0"))]

    _next_input = _msvcrt.getwch

    _set_terminal_raw = _contextlib.nullcontext

    _input_ready = _msvcrt.kbhit

except ImportError:  # Unix
    import sys as _sys, tty as _tty, termios as _termios, \
        select as _select, functools as _functools

    # Length 0 sequences, length 1 sequences...
    _ESCAPE_SEQUENCES = [
        frozenset(("\x1b",)),
        frozenset(("\x1b\x5b", "\x1b\x4f"))]

    @_contextlib.contextmanager
    def _set_terminal_raw():
        fd = _sys.stdin.fileno()
        old_settings = _termios.tcgetattr(fd)
        try:
            _tty.setraw(_sys.stdin.fileno())
            yield
        finally:
            _termios.tcsetattr(fd, _termios.TCSADRAIN, old_settings)

    _next_input = _functools.partial(_sys.stdin.read, 1)

    def _input_ready():
        return _select.select([_sys.stdin], [], [], 0) == ([_sys.stdin], [], [])

_MAX_ESCAPE_SEQUENCE_LENGTH = len(_ESCAPE_SEQUENCES)

def _get_keystroke():
    key = _next_input()
    while (len(key) <= _MAX_ESCAPE_SEQUENCE_LENGTH and
           key in _ESCAPE_SEQUENCES[len(key)-1]):
        key += _next_input()
    return key

def _flush():
    while _input_ready():
        _next_input()

def key_pressed(key: str = None, *, flush: bool = True) -> bool:
    """Return True if the specified key has been pressed

    Args:
        key: The key to check for. If None, any key will do.
        flush: If True (default), flush the input buffer after the key was found.
    
    Return:
        boolean stating whether a key was pressed.
    """
    with _set_terminal_raw():
        if key is None:
            if not _input_ready():
                return False
            if flush:
                _flush()
            return True

        while _input_ready():
            keystroke = _get_keystroke()
            if keystroke == key:
                if flush:
                    _flush()
                return True
        return False

def print_key() -> None:
    """Print the key that was pressed
    
    Useful for debugging and figuring out keys.
    """
    with _set_terminal_raw():
        _flush()
        print("\\x" + "\\x".join(map("{:02x}".format, map(ord, _get_keystroke()))))

def wait_key(key=None, *, pre_flush=False, post_flush=True) -> str:
    """Wait for a specific key to be pressed.

    Args:
        key: The key to check for. If None, any key will do.
        pre_flush: If True, flush the input buffer before waiting for input.
        Useful in case you wish to ignore previously pressed keys.
        post_flush: If True (default), flush the input buffer after the key was
        found. Useful for ignoring multiple key-presses.
    
    Returns:
        The key that was pressed.
    """
    with _set_terminal_raw():
        if pre_flush:
            _flush()

        if key is None:
            key = _get_keystroke()
            if post_flush:
                _flush()
            return key

        while _get_keystroke() != key:
            pass
        
        if post_flush:
            _flush()

        return key

您可以在while循环中使用key_pressed()

while True:
    time.sleep(5)
    if key_pressed():
        break

您可以检查特定的键:
while True:
    time.sleep(5)
    if key_pressed("\x00\x48"):  # Up arrow key on Windows.
        break

使用print_key()查找特殊键:

>>> print_key()
# Press up key
\x00\x48

或者等待直到按下某个键:

>>> wait_key("a") # Stop and ignore all inputs until "a" is pressed.

2
import cv2

key = cv2.waitKey(1)

这段话来自于 openCV 软件包。其中的 delay 参数表示等待按键的毫秒数,此处为1ms。根据文档pollKey() 函数可以在不等待的情况下使用。

6
请您提供原文,我将为您进行翻译。 - Valentyn
6
这个功能来说,一个35MB的计算机视觉模块和对numpy的依赖似乎有点大了。 - Marc L.

1
你可以使用 pygameget_pressed() 函数:
import pygame

while True:
    keys = pygame.key.get_pressed()
    if (keys[pygame.K_LEFT]):
        pos_x -= 5
    elif (keys[pygame.K_RIGHT]):
        pos_x += 5
    elif (keys[pygame.K_UP]):
        pos_y -= 5
    elif (keys[pygame.K_DOWN]):
        pos_y += 5

0

我正在寻找如何连续检测不同的按键,直到例如Ctrl + C中断程序从而停止监听并相应不同的按键。

使用以下代码:

while True:
    if keyboard.is_pressed("down"):
        print("Reach the bottom!")
    if keyboard.is_pressed("up"):
        print("Reach the top!")
    if keyboard.is_pressed("ctrl+c"):
        break

如果我按下 向下箭头向上箭头,它会导致程序不断地发送响应文本。我认为这是因为它在 while 循环中,即使你只按一次,它也会被触发多次(正如 doc 中所述,我在阅读后意识到了这一点)。

那时,我还没有去阅读文档,我尝试添加 time.sleep()

while True:
    if keyboard.is_pressed("down"):
        print("Reach the bottom!")
        time.sleep(0.5)
    if keyboard.is_pressed("up"):
        print("Reach the top!")
        time.sleep(0.5)
    if keyboard.is_pressed("ctrl+c"):
        break

这解决了垃圾邮件问题。

但是,这不是一个很好的方法,因为在箭头键上连续快速按下时,只会触发一次,而不是我按下的次数,因为程序将休眠0.5秒,意味着发生在那个0.5秒的“键盘事件”将不被计算。

所以,我继续阅读文档并想到在这部分中执行此操作的想法。

while True:
    # Wait for the next event.
    event = keyboard.read_event()
    if event.event_type == keyboard.KEY_DOWN and event.name == 'down':
        # do whatever function you wanna here
    if event.event_type == keyboard.KEY_DOWN and event.name == 'up':
        # do whatever function you wanna here
    if keyboard.is_pressed("ctrl+c"):
        break

现在,它运行得很好! 说实话,我没有深入研究过文档,以前用过,但我已经忘记了内容,如果您知道或找到任何更好的方法来执行类似的功能,请告诉我!

谢谢,祝您今天过得愉快!


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接