如何检测按键?

Posted

技术标签:

【中文标题】如何检测按键?【英文标题】:How to detect key presses? 【发布时间】:2014-07-27 04:33:57 【问题描述】:

我正在用 Python 制作一个秒表类型的程序,我想知道如何检测是否按下了某个键(例如 p 表示暂停,s 表示停止),我不希望它像raw_input 这样在继续执行之前等待用户的输入。

有人知道如何在 while 循环中执行此操作吗?

我想做这个跨平台的,但如果这不可能,那么我的主要开发目标是 Linux。

【问题讨论】:

对于 OS X ***.com/a/47197390/5638869 适用于 Python 2 和 3 【参考方案1】:

Python 有一个 keyboard 模块,它有很多特性。安装它,也许用这个命令:

pip3 install keyboard

然后在如下代码中使用它:

import keyboard  # using module keyboard
while True:  # making a loop
    try:  # used try so that if user pressed other than the given key error will not be shown
        if keyboard.is_pressed('q'):  # if key 'q' is pressed 
            print('You Pressed A Key!')
            break  # finishing the loop
    except:
        break  # if user pressed a key other than the given key the loop will break

【讨论】:

我不确定是否适用于 linux,但它适用于我的 Windows。 keyboard 在 linux 中显然需要 root :/ "为避免依赖 X,Linux 部分读取原始设备文件 (/dev/input/input*),但这需要 root。" 我不明白为什么 try: except: 有用。 这个解决方案似乎使用了大量的 CPU。就我一个人吗?【参考方案2】:

对于那些在 Windows 上苦苦寻找有效答案的人,这里是我的:pynput

from pynput.keyboard import Key, Listener

def on_press(key):
    print('0 pressed'.format(
        key))

def on_release(key):
    print('0 release'.format(
        key))
    if key == Key.esc:
        # Stop listener
        return False

# Collect events until released
with Listener(
        on_press=on_press,
        on_release=on_release) as listener:
    listener.join()

上面的功能将打印您按下的任何键,并在您释放“esc”键时开始执行操作。键盘文档是here,用于更多样化的用法。

Markus von Broady 强调了一个潜在的问题,即:此答案不需要您在当前窗口中激活此脚本,Windows 的解决方案是:

from win32gui import GetWindowText, GetForegroundWindow
current_window = (GetWindowText(GetForegroundWindow()))
desired_window_name = "Stopwatch" #Whatever the name of your window should be

#Infinite loops are dangerous.
while True: #Don't rely on this line of code too much and make sure to adapt this to your project.
    if current_window == desired_window_name:

        with Listener(
            on_press=on_press,
            on_release=on_release) as listener:
            listener.join()

【讨论】:

@nimig18 ...并且不需要root :) 此解决方案存在问题(不确定替代方案):不必在控制台窗口内按下该键即可使其生效。想象一下,有一个脚本在按下 ESC 之前执行某些工作,然后你在另一个程序中按下它。 @MarkusvonBroady 我想 win32gui 足以解决它,我已经编辑了我的答案,至少可以解决 Windows 用户的问题。 @Mitrek 我试过这个,但我的代码停止了进一步的执行并卡在这里。它像输入()一样工作。我有在 selenium、firefox 中执行的代码,但是一旦遇到这个序列,就没有进一步的动作了。 应该是公认的答案,因为它适用于 linux 和 windows【参考方案3】:

更多的事情可以通过 keyboard 模块完成。 您可以使用pip install keyboard 安装此模块 以下是一些方法:


方法#1:

使用函数read_key()

import keyboard

while True:
    if keyboard.read_key() == "p":
        print("You pressed p")
        break

这将在按下 p 键时中断循环。


方法#2:

使用函数wait:

import keyboard

keyboard.wait("p")
print("You pressed p")

它会等待你按下 p 并在按下时继续代码。


方法#3:

使用函数on_press_key

import keyboard

keyboard.on_press_key("p", lambda _:print("You pressed p"))

它需要一个回调函数。我使用了_,因为键盘函数将键盘事件返回给该函数。

一旦执行,它会在按键被按下时运行该函数。您可以通过运行此行来停止所有挂钩:

keyboard.unhook_all()

方法#4:

user8167727 已经回答了这种方法,但我不同意他们编写的代码。它将使用函数is_pressed,但以另一种方式:

import keyboard

while True:
    if keyboard.is_pressed("p"):
        print("You pressed p")
        break

按下 p 会中断循环。


方法#5:

您也可以使用keyboard.record。它记录所有按下和释放的键,直到您按下escape 键或您在until arg 中定义的键,并返回keyboard.KeyboardEvent 元素列表。

import keyboard

keyboard.record(until="p")
print("You pressed p")

注意事项:

keyboard 将从整个操作系统读取按键。 keyboard 在 linux 上需要 root

【讨论】:

使用键盘模块的最大缺点是它要求您以ROOT用户身份运行。这使得该模块在我的代码中被禁止使用。仅轮询是否已按下某个键不需要 root 权限。我已阅读文档并了解模块中存在限制的原因,但如果您只需要轮询密钥,请查看其他地方... 非常有用的信息分享,先生!我想知道我是否可以使用keyboard.wait() 等待多个键,如果其中任何一个被按下则继续 @PreetkaranSingh wait() 不提供此功能。您将不得不使用 keyboard.read_key() 并在 while 循环中包含一个 if 条件。见方法#1 谢谢先生!,您想了解一下suppress 关键字在keyboard.read_key() 中的用法,什么时候使用它,什么时候不使用它...... @PreetkaranSingh 我会,但我没有足够的关于抑制参数的信息【参考方案4】:

正如 OP 提到的 raw_input - 这意味着他想要 cli 解决方案。 Linux:curses 是您想要的(Windows PDCurses)。 Curses,是一个用于cli软件的图形API,你可以实现的不仅仅是检测关键事件。

此代码将检测按键,直到按下新行。

import curses
import os

def main(win):
    win.nodelay(True)
    key=""
    win.clear()                
    win.addstr("Detected key:")
    while 1:          
        try:                 
           key = win.getkey()         
           win.clear()                
           win.addstr("Detected key:")
           win.addstr(str(key)) 
           if key == os.linesep:
              break           
        except Exception as e:
           # No input   
           pass         

curses.wrapper(main)

【讨论】:

这真是太好了。在遇到它之前必须永远搜索。似乎比使用termios 等等更干净...... 需要添加import os才能退出示例。 如果你使用win.nodelay(False) 而不是True,它每秒不会产生一百万个异常。 丑陋无比,但比我见过的其他任何东西都要漂亮。奇怪的是我清楚地记得在我的 python2.7 天打开文件描述符 0 (stdin) 以进行非阻塞读取并让它表现为一个按键收集器,但对于我的一生,我无法弄清楚如何我做到了。我确实记得这一切都是从我分离标准输入开始的,但后来意识到我可以简单地将它作为一个单独的流打开,而不必担心崩溃或将其状态恢复为原始行为。仍然......它是如此简单和优雅,现在,如何???找不到。【参考方案5】:

对于Windows,您可以像这样使用msvcrt

   import msvcrt
   while True:
       if msvcrt.kbhit():
           key = msvcrt.getch()
           print(key)   # just to show the result

【讨论】:

msvcrt 是一个仅限 Windows 的模块。 我现在实际上在使用 pynput,这可能是一个更好的答案 请注意,要在 OS X(不了解 Linux)上运行的 pynput 必须以 root 身份运行才能运行。对某些人来说,这可能是一个非首发。 我可以发誓这个问题是针对“跨平台”或“Linux”...【参考方案6】:

使用此代码查找按下的键

from pynput import keyboard

def on_press(key):
    try:
        print('alphanumeric key 0 pressed'.format(
            key.char))
    except AttributeError:
        print('special key 0 pressed'.format(
            key))

def on_release(key):
    print('0 released'.format(
        key))
    if key == keyboard.Key.esc:
        # Stop listener
        return False

# Collect events until released
with keyboard.Listener(
        on_press=on_press,
        on_release=on_release) as listener:
    listener.join()

【讨论】:

这就是问题所在,我使用的是 macOS 并分别安装了 pynput 和键盘,程序运行没有任何错误,但只能检测(在 python shell 上)特殊键。没有检测到字母数字键,相反,被认为是我在 shell 上编写代码。你知道可能是什么问题吗? 相同的代码在 shell 中为我工作。请检查一下。键盘包不需要此代码。 这是 linux 的方式,因为键盘库需要 root。 此解决方案将检测 all 击键;也发生在不同的终端窗口中。不幸的是,这严重限制了它可能的用例。 对我来说只是超时【参考方案7】:

使用PyGame有一个窗口,然后你可以得到关键事件。

致信p

import pygame, sys
import pygame.locals

pygame.init()
BLACK = (0,0,0)
WIDTH = 1280
HEIGHT = 1024
windowSurface = pygame.display.set_mode((WIDTH, HEIGHT), 0, 32)

windowSurface.fill(BLACK)

while True:
    for event in pygame.event.get():
        if event.key == pygame.K_p: # replace the 'p' to whatever key you wanted to be pressed
             pass #Do what you want to here
        if event.type == pygame.locals.QUIT:
             pygame.quit()
             sys.exit()

【讨论】:

我无法运行上述代码。我首先必须检查事件类型是 KEYUP 或 KEYDOWN 之一: if event.type in (pygame.KEYDOWN, pygame.KEYUP): print("Key: ", event.key) if(event.key == pygame .K_q): pygame.quit()【参考方案8】:

neoDev 对问题本身的评论可能很容易被忽略,但它链接到此处任何答案中未提及的解决方案。

使用此解决方案无需导入keyboard

从this other question 复制的解决方案,所有归功于@neoDev。

这在 macOS Sierra 和 Python 2.7.10 和 3.6.3 上对我有用

import sys,tty,os,termios
def getkey():
    old_settings = termios.tcgetattr(sys.stdin)
    tty.setcbreak(sys.stdin.fileno())
    try:
        while True:
            b = os.read(sys.stdin.fileno(), 3).decode()
            if len(b) == 3:
                k = ord(b[2])
            else:
                k = ord(b)
            key_mapping = 
                127: 'backspace',
                10: 'return',
                32: 'space',
                9: 'tab',
                27: 'esc',
                65: 'up',
                66: 'down',
                67: 'right',
                68: 'left'
            
            return key_mapping.get(k, chr(k))
    finally:
        termios.tcsetattr(sys.stdin, termios.TCSADRAIN, old_settings)
try:
    while True:
        k = getkey()
        if k == 'esc':
            quit()
        else:
            print(k)
except (KeyboardInterrupt, SystemExit):
    os.system('stty sane')
    print('stopping.')

【讨论】:

【参考方案9】:

你没有提到这是否是一个 GUI 程序,但大多数 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()

通过上述方式,当您在 Text 小部件中键入内容时,会为您按下的每个(或几乎每个)键调用 key_handler 例程。

【讨论】:

【参考方案10】:

我根据这篇文章制作了这种游戏(使用 msvcr 库和 Python 3.7)。

以下是游戏的主要功能,即检测按键:

import msvcrt

def _secret_key(self):
    # Get the key pressed by the user and check if he/she wins.

    bk = chr(10) + "-"*25 + chr(10)

    while True:
        print(bk + "Press any key(s)" + bk)
        #asks the user to type any key(s)

        kp = str(msvcrt.getch()).replace("b'", "").replace("'", "")
        # Store key's value.

        if r'\xe0' in kp:
            kp += str(msvcrt.getch()).replace("b'", "").replace("'", "")
            # Refactor the variable in case of multi press.

        if kp == r'\xe0\x8a':
            # If user pressed the secret key, the game ends.
            # \x8a is CTRL+F12, that's the secret key.

            print(bk + "CONGRATULATIONS YOU PRESSED THE SECRET KEYS!\a" + bk)
            print("Press any key to exit the game")
            msvcrt.getch()
            break
        else:
            print("    You pressed:'", kp + "', that's not the secret key(s)\n")
            if self.select_continue() == "n":
                if self.secondary_options():
                    self._main_menu()
                break

如果你想要程序的完整源代码可以查看或下载from GitHub

秘钥是:

Ctrl+F12

【讨论】:

【参考方案11】:

使用keyboard 包,尤其是在Linux 上不是一个合适的解决方案,因为该包需要root 权限才能运行。我们可以使用getkey package 轻松实现这一点。这类似于 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)

【讨论】:

根据该项目显示的几个问题,getkey 似乎不再积极维护,并且 Windows 上的 pip install 已损坏。【参考方案12】:
import cv2

key = cv2.waitKey(1)

这是来自openCV 包。 delay arg 是等待按键的毫秒数。在这种情况下,1ms。 Per the docs,pollKey()不用等待就可以使用了。

【讨论】:

你需要写更多关于它应该如何工作的内容。此外,如果您解释为什么“key”和“1”在此示例中的含义,将会很有帮助。我无法让这个例子起作用。 一个 35MB 的计算机视觉模块 + 对 numpy 的依赖对于这一点功能来说似乎是很多包袱。【参考方案13】:

curses 模块完成了这项工作。

您可以从终端运行此示例对其进行测试:

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()

【讨论】:

这个手柄是否同时按下了左箭头和上箭头?【参考方案14】:

即使通过 ssh 也可以工作的非 root 版本:sshkeyboard。使用pip install sshkeyboard 安装,

然后编写脚本如:

from sshkeyboard import listen_keyboard

def press(key):
    print(f"'key' pressed")

def release(key):
    print(f"'key' released")

listen_keyboard(
    on_press=press,
    on_release=release,
)

它会打印出来:

'a' pressed
'a' released

A 键被按下时。 ESC 键默认结束监听。

与curses、tkinter 和getch 相比,它需要更少的编码。而且它不需要像键盘模块那样的root访问权限。

【讨论】:

【参考方案15】:

你可以使用pygame的get_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

【讨论】:

【参考方案16】:

这是一个跨平台的解决方案,包括阻塞和非阻塞,不需要任何外部库:

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.

【讨论】:

以上是关于如何检测按键?的主要内容,如果未能解决你的问题,请参考以下文章

如何检测按键?

如何检测 Python 进程中的按键?

如何在Java中检测按键

如何使用 QxtGlobalShortcut 库检测 Qt 中的按键释放事件

如何检测vaadin TextArea中的输入按键

如何检测textarea上箭头键的按键事件?