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

如何从控制台 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 的可移植性也不错,尽管不是必需的。

151955 次浏览

You might look at how pygame handles this to steal some ideas.

The standard approach is to use the select module.

However, this doesn't work on Windows. For that, you can use the msvcrt module's keyboard polling.

Often, this is done with multiple threads -- one per device being "watched" plus the background processes that might need to be interrupted by the device.

From the comments:

import msvcrt # built-in module


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

Thanks for the help. I ended up writing a C DLL called PyKeyboardAccess.dll and accessing the crt conio functions, exporting this routine:

#include <conio.h>


int kb_inkey () {
int rc;
int key;


key = _kbhit();


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


return rc;
}

And I access it in python using the ctypes module (built into python 2.5):

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)

Ok, since my attempt to post my solution in a comment failed, here's what I was trying to say. I could do exactly what I wanted from native Python (on Windows, not anywhere else though) with the following code:

import msvcrt


def kbfunc():
x = msvcrt.kbhit()
if x:
ret = ord(msvcrt.getch())
else:
ret = 0
return ret
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

A solution using the curses module. Printing a numeric value corresponding to each key pressed:

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)

If you combine time.sleep, threading.Thread, and sys.stdin.read you can easily wait for a specified amount of time for input and then continue, also this should be cross-platform compatible.

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

You could also place this into a function like so

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

Although this will not return anything so instead you should use the multiprocessing pool module you can find that here: how to get the return value from a thread in python?

None of these answers worked well for me. This package, pynput, does exactly what I need.

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

I am using this for checking for key presses, can't get much simpler:

#!/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)

While curses is not working on windows, there is a 'unicurses' version, supposedly working on Linux, Windows, Mac but I could not get this to work

I've come across a cross-platform implementation of kbhit at http://home.wlu.edu/~levys/software/kbhit.py (made edits to remove irrelevant code):

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 != []

Make sure to read() the waiting character(s) -- the function will keep returning True until you do!

This can be done using 'pynput' module in python, You press a key and it gets printed It's that easy!

  1. PIP Install the module in command prompt, write following text and press enter

    pip install pynput

  2. Run the following code:

    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()
    
  3. You can end the loop with any key you want by changing Key.enter to some other key in the 8th line of the code.

One more option would be to use sshkeyboard library to enable reacting to key presses instead of polling them periodically, and potentially missing the key press:

from sshkeyboard import listen_keyboard, stop_listening


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


listen_keyboard(on_press=press)

Simply pip install sshkeyboard to use it.