在python中轮询键盘(检测按键)

Posted

技术标签:

【中文标题】在python中轮询键盘(检测按键)【英文标题】:Polling the keyboard (detect a keypress) in python 【发布时间】:2010-09-22 11:14:52 【问题描述】:

如何从控制台 python 应用程序轮询键盘?具体来说,我想在许多其他 I/O 活动(套接字选择、串行端口访问等)中做一些类似的事情:

while True:
    # doing amazing pythonic embedded stuff
    # ...

    # periodically do a non-blocking check to see if
    # we are being told to do something else
    x = keyboard.read(1000, timeout = 0)

    if len(x):
        # ok, some key got pressed
        # do something

在 Windows 上执行此操作的正确 Python 方法是什么?此外,Linux 的可移植性也不错,尽管这不是必需的。

【问题讨论】:

只是为了让其他人知道,我发现大多数涉及选择或线程库的解决方案在 IDLE 中都无法正常工作。但是,它们 all 在 CLI 上运行良好,即 python /home/pi/poll_keyboard.py 总的来说,我认为对按键做出反应而不是定期轮询它们是更强大的解决方案,因为您不会错过按键。请参阅下面的答案。 【参考方案1】:

标准方法是使用select 模块。

但是,这在 Windows 上不起作用。为此,您可以使用msvcrt 模块的键盘轮询。

通常,这是通过多个线程来完成的——每个设备一个被“监视”加上可能需要被设备中断的后台进程。

【讨论】:

如果我错了,请纠正我,但根据我的经验,msvcrt 仅在您在命令窗口中运行程序时才有效,即。不在 IDLE 中,也没有 GUI... @digitalHamster0:任何用自定义对象(例如 IDLE、大多数 GUI)替换 sys.stdin 的东西都会产生这种效果。当sys.stdin不是真正的文件时,你不能使用select;当它没有连接到“真实”控制台时,您不能使用msvcrt 键盘轮询功能(隐式依赖“真实”控制台)。 总的来说,我认为对按键做出反应而不是定期轮询它们是更强大的解决方案,因为您不会错过按键。请参阅下面的答案。【参考方案2】:

使用 curses 模块的解决方案。打印与按下的每个键对应的数值:

import curses

def main(stdscr):
    # do not wait for input when calling getch
    stdscr.nodelay(1)
    while True:
        # get keyboard input, returns -1 if none available
        c = stdscr.getch()
        if c != -1:
            # print numeric value
            stdscr.addstr(str(c) + ' ')
            stdscr.refresh()
            # return curser to start position
            stdscr.move(0, 0)

if __name__ == '__main__':
    curses.wrapper(main)

【讨论】:

OZ123:可以。见***.com/questions/32417379/… 在无头主机上通过 SSH 术语使用诅咒时遇到问题。问题严重扰乱了终端 - 要求它在每次运行之间为reset。它确实有效,即检测按键。必须有更智能的解决方案。【参考方案3】:

好的,因为我试图在评论中发布我的解决方案失败了,这就是我想说的。我可以使用以下代码从本机 Python(在 Windows 上,但在其他任何地方都没有)做我想要的:

import msvcrt 

def kbfunc(): 
   x = msvcrt.kbhit()
   if x: 
      ret = ord(msvcrt.getch()) 
   else: 
      ret = 0 
   return ret

【讨论】:

【参考方案4】:

这些答案都不适合我。这个包,pynput,正是我需要的。

https://pypi.python.org/pypi/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()

【讨论】:

这对我有用,除了按下的键在按下后立即回显到屏幕上,并且无法禁用它。 github.com/moses-palmer/pynput/issues/47 另外,当程序退出时,字符会被缓冲并额外出现在命令行上。这似乎是 Linux 实现的限制,但在 Windows 上可以正常工作。 当脚本通过 ssh 运行时,此解决方案不起作用。它出现错误:'Xlib.error.DisplayNameError: Bad display name "".' 正如 David 上面提到的 - 这对于无头实例来说不是一个好的解决方案,因为它依赖于 Xserver。 import Xlib.display【参考方案5】:
import sys
import select

def heardEnter():
    i,o,e = select.select([sys.stdin],[],[],0.0001)
    for s in i:
        if s == sys.stdin:
            input = sys.stdin.readline()
            return True
    return False

【讨论】:

没有工作。出现错误:select.error: (10093, '要么应用程序没有调用 WSAStartup,要么 WSAStartup 失败') 我多次听说 MS Windows 上的 select 系统调用不支持常规文件描述符,仅适用于套接字。 (我不知道 select() 的 Python 实现是否曾经在幕后解决过这个问题)。 对我来说,这只会在我按下 Enter 后检测到按键。 @MarkSmith:那是因为程序在按下 enter 或 control-D (*) 之前没有收到输入,它仍然在内核的“行编辑缓冲区”中。 (如果在缓冲区中没有字符的情况下按 control-D,它将关闭终端。) // 为了在类 unix 系统上工作,终端必须设置为“raw”或“cbreak”模式而不是“cooked” '。我认为这是通过 stdin 上的一些 ioctl 完成的。 @JonathanHartley:(见我之前的评论。)【参考方案6】:

来自cmets:

import msvcrt # built-in module

def kbfunc():
    return ord(msvcrt.getch()) if msvcrt.kbhit() else 0

感谢您的帮助。我最终编写了一个名为 PyKeyboardAccess.dll 的 C DLL 并访问了 crt conio 函数,导出了这个例程:

#include <conio.h>

int kb_inkey () 
   int rc;
   int key;

   key = _kbhit();

   if (key == 0) 
      rc = 0;
    else 
      rc = _getch();
   

   return rc;

我使用 ctypes 模块(内置于 python 2.5)在 python 中访问它:

import ctypes
import time

# first, load the DLL
try:
    kblib = ctypes.CDLL("PyKeyboardAccess.dll")
except:
    raise ("Error Loading PyKeyboardAccess.dll")

# now, find our function
try:
    kbfunc = kblib.kb_inkey
except:
    raise ("Could not find the kb_inkey function in the dll!")

# Ok, now let's demo the capability  
while True:
    x = kbfunc()

    if x != 0:
        print "Got key: %d" % x
    else:
        time.sleep(.01)

【讨论】:

这比内置的 msvcrt.kbhit() 有什么好处?它有什么优势? 你完全正确!我看错了你的帖子;我没有意识到有一个名为 msvcrt 的 python 模块!我只是以为您的意思是“使用 ms crt”,然后我开始考虑线程并且没有连接点。你是绝对正确的。 我做了同样的事情: import msvcrt def kbfunc(): x = msvcrt.kbhit() if x: ret = ord(msvcrt.getch()) else: ret = 0 return ret 请不要使用这样的 lambda。 "x = lambda" 应该拼写为 "def x():" 保存 lambda 会混淆 n00bz 并让有经验的人疯狂地试图解释它。 哈哈!那不是 lambda。这就是“cmets”字段如何重新格式化我将代码放入评论的尝试。顺便说一句,保存 lambda 也让我感到困惑,而且我不是 python n00b :-)【参考方案7】:

我在http://home.wlu.edu/~levys/software/kbhit.py 遇到了kbhit 的跨平台实现(进行了编辑以删除不相关的代码):

import os
if os.name == 'nt':
    import msvcrt
else:
    import sys, select

def kbhit():
    ''' Returns True if a keypress is waiting to be read in stdin, False otherwise.
    '''
    if os.name == 'nt':
        return msvcrt.kbhit()
    else:
        dr,dw,de = select.select([sys.stdin], [], [], 0)
        return dr != []

确保 read() 等待字符 - 该函数将一直返回 True 直到你这样做!

【讨论】:

这仍然是最新的吗?当我调用选择版本时,我总是会在 dr 中恢复内容。如果它仍然有效,你能把它放在上下文中吗?我有一个“while true”循环,如果按下某个键,我想摆脱它。 @Mastiff 也许您在按照建议检测到等待字符后不会read()【参考方案8】:

你可以看看pygame 是如何处理这个来窃取一些想法的。

【讨论】:

PyGame 事件处理仅适用于 GUI,而不是 OP 要求的控制台。【参考方案9】:

我用它来检查按键,再简单不过了:

#!/usr/bin/python3
# -*- coding: UTF-8 -*-

import curses, time

def main(stdscr):
    """checking for keypress"""
    stdscr.nodelay(True)  # do not wait for input when calling getch
    return stdscr.getch()

while True:
    print("key:", curses.wrapper(main)) # prints: 'key: 97' for 'a' pressed
                                        # '-1' on no presses
    time.sleep(1)

虽然 curses 不能在 Windows 上运行,但有一个“unicurses”版本,据说可以在 Linux、Windows、Mac 上运行,但我无法让它运行

【讨论】:

PyPI 上还有windows-curses【参考方案10】:

这可以使用 python 中的 'pynput' 模块来完成, 你按一个键,它就会被打印出来。就这么简单!

    PIP 在命令提示符下安装模块,输入以下文本并回车

    pip install pynput

    运行以下代码:

    from pynput.keyboard import Key, Listener
    
    def pressed(key):
        print('Pressed:',key)
    
    def released(key):
        print('Released:',key)
        if key == Key.enter:
            # Stop detecting when enter key is pressed
            return False
    
    # Below loop for Detcting keys runs until enter key is pressed
    with Listener(on_press=pressed, on_release=released) as detector:
        detector.join()
    

    您可以通过将代码第 8 行中的 Key.enter 更改为其他键来使用您想要的任何键结束循环。

【讨论】:

【参考方案11】:

另一种选择是使用sshkeyboard 库来启用对按键的反应,而不是定期轮询它们,并且可能会错过按键:

from sshkeyboard import listen_keyboard, stop_listening

def press(key):
    print(f"'key' pressed")
    if key == "z":
        stop_listening()

listen_keyboard(on_press=press)

只需pip install sshkeyboard 即可使用。

【讨论】:

【参考方案12】:

如果将 time.sleep、threading.Thread 和 sys.stdin.read 结合使用,您可以轻松地等待指定的输入时间然后继续, 这也应该是跨平台兼容的。

t = threading.Thread(target=sys.stdin.read(1) args=(1,))
t.start()
time.sleep(5)
t.join()

你也可以把它放到这样的函数中

def timed_getch(self, bytes=1, timeout=1):
    t = threading.Thread(target=sys.stdin.read, args=(bytes,))
    t.start()
    time.sleep(timeout)
    t.join()
    del t

虽然这不会返回任何内容,但您应该使用多处理池模块,您可以在此处找到:how to get the return value from a thread in python?

【讨论】:

第一行不应该是:t = threading.Thread(target=sys.stdin.read, args=(1,)) 这个解决方案不会总是休眠 5 秒,即使用户在此之前按下了一个键?

以上是关于在python中轮询键盘(检测按键)的主要内容,如果未能解决你的问题,请参考以下文章

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

在c#中轮询一个url

python如何用按键控制程序开始?

是否可以检测 iPad 智能键盘上的按键和释放?

obj -c IOS检测大写锁定按键事件

如何将用户输入作为按键,检测它,并使程序在Python中相应地运行?