如何检测按键?

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

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

138

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

6
我对于Linux不确定,但对于我来说在Windows上可以运行。 - user8167727
200
在Linux中,似乎需要root权限才能使用键盘:/ - Inaimathi
7
为避免依赖于X,Linux部件读取原始设备文件(/dev/input/input*),但这需要root权限。 - jrouquie
11
我不明白为什么try: except:语句有用。 - TypicalHog
4
这个解决方案似乎在使用大量的CPU,只有我遇到这个问题吗? - jason
显示剩余9条评论

101
对于那些在Windows上苦苦寻找可行答案的人,这是我的建议:pynput
这里是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'键时启动一个动作。键盘文档在这里,以获取更多不同的用法。 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()

13
不需要root权限 :) - c z
6
这个解决方案有问题(不确定是否有替代方案):键不必在控制台窗口内按下才能生效。想象一下,你有一个脚本会一直运行,直到按下 ESC 键停止,但是如果你在另一个程序中按下了它,那么脚本就会停止。 - Markus von Broady
1
@MarkusvonBroady 我想win32gui就足以解决它了,我已经编辑了我的答案,以一种可能至少对Windows用户解决它的方式。 - Mitrek
2
应该被接受为正确答案,因为它在Linux和Windows上都能工作。 - Akash Karnatak
我执行了 pip install pynput 命令,它安装了 某些东西,但是当我运行你的代码时,出现了这个错误:No connection could be made because the target machine actively refused it - john k
显示剩余5条评论

89

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


方法 #1:

使用函数read_key()

import keyboard

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

当按下键盘上的 p 键时,将会打破循环。


方法二:

使用函数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()

方法四:

这种方法与user8167727已经提供的答案类似,但我不同意他们的代码。这将使用函数is_pressed,但使用另一种方式:

import keyboard

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

按下 p 键时,它将打破循环。


方法#5:

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

import keyboard

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

注意:

  • keyboard将从整个操作系统读取按键记录。
  • keyboard在Linux上需要root权限。


57
使用keyboard模块最大的负面影响是需要作为ROOT用户运行。这使得该模块在我的代码中被禁用。仅仅为了轮询按键是否被按下并不需要root权限。我已经阅读了文档并理解该模块存在限制的原因,但如果您只需要轮询按键,请寻找其他方法... - muman
1
非常有用的信息,先生!我想知道是否可以使用keyboard.wait()等待多个按键,如果其中任何一个被按下,则继续执行。 - Preetkaran Singh
1
谢谢您!请问您能否解释一下在keyboard.read_key()中使用suppress关键字的用法,以及何时使用和何时不使用它... - Preetkaran Singh
在大多数Linux系统上,事件设备在input组中,因此您只需要将用户添加到附加组即可。 - Patrick B.
sshkeyboard 在 Linux 上不需要 root 或 X 服务器,也可以在 Windows 上运行。 - ilon
显示剩余5条评论

52

正如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)

2
这真的很好。在找到它之前,我不得不永远搜索。似乎比使用“termios”等进行黑客攻击要干净得多... - Hugh Perkins
6
需要添加 import os 才能够退出示例。 - malte
1
如果你使用win.nodelay(False)代替True,它就不会每秒产生一百万个异常。 - Johannes Hoff
丑陋如同任何东西,但仍然比我见过的任何东西更美丽。奇怪的是,我清楚地记得在我的python2.7时代,使用非阻塞方式打开文件描述符0(stdin),并将其作为键按下收集器,而且它表现得很好。但是就我而言,我却想不起来我是如何做到的。我记得一切都始于我分离stdin,但后来意识到我可以简单地将其作为单独的流打开,而不必担心崩溃或将其状态返回到原始行为。尽管如此...它是如此简单和优雅,但现在我却找不到它了。 - Ismael Harun
这会在终端上覆盖一个CLI“窗口”,它能在不显示此“窗口”的情况下使用吗? - Fosfor
显示剩余2条评论

30

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

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

10
msvcrt是一个仅限于Windows操作系统的模块。 - Dunatotatos
1
我现在实际上使用pynput,这可能是一个更好的答案。 - 56-
请注意,为了使pynput在OS X上运行(不确定Linux),必须以root身份运行才能正常工作。这可能对某些人来说是无法接受的。 - Gabe Weiss
我本以为这个问题是关于“跨平台”或“Linux”的... - Aaron Mann

16

使用此代码以查找按下的键

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和keyboard,程序运行时没有任何错误,但只能检测到(在python shell上)特殊键。字母数字键未被检测到,相反,它们被视为我在shell上编写代码。您知道可能是什么问题吗? - Dario Deniz Ergün
相同的代码在我的shell中运行正常,请检查一下。键盘包不需要这段代码。 - Manivannan Murugavel
2
这是在 Linux 中的正确方式,因为键盘库需要 root 权限。 - David
4
这个解决方案将检测到所有按键输入,包括在不同终端窗口中发生的按键。不幸的是,这严重限制了它可能的使用情景。 - Serge Stroobandt
它对我来说只是超时了。 - Trevor Blythe

14

neoDev在问题本身的评论可能很容易被忽略,但它链接到的解决方案在这里的任何答案中都没有提到。

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

解决方案从另一个问题中复制,所有功劳归@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.')

喜欢在 macOS 上使用这个。谢谢。 - Cam U
哇,这太完美了。键盘需要root访问权限,pynput需要X Server。而这个东西既不需要,也可以通过ssh在CLI中为非root用户工作。在Debian 11上测试过,Python 3+可用。 - AJ Smith 'Smugger'

9
使用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()

6
我无法运行以上代码。我首先要检查事件类型是否为KEYUP或KEYDOWN: if event.type in (pygame.KEYDOWN, pygame.KEYUP): print("按键:", event.key) 如果(event.key == pygame.K_q):pygame.quit() - Jonathan Allin

5

非root版本,可通过ssh运行: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 权限。

重要提示:在Windows上,只有在从Windows命令行直接运行程序时才有效(据我所知)。如果您尝试通过IDE运行它,则会失败。 - Calvin Godfrey

5

我基于这篇文章制作了一个游戏(使用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

如果您想获取该程序的源代码,您可以查看它或从GitHub下载:https://github.com/Ferd656/The_secret_key_game/ 秘密按键是:Ctrl+F12

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