如何检测按键?
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.
【讨论】:
以上是关于如何检测按键?的主要内容,如果未能解决你的问题,请参考以下文章