Как я могу опросить клавиатуру из консольного приложения Python? В частности, я хотел бы сделать что-то похожее на это посреди множества других операций ввода-вывода (выбор сокета, доступ к последовательному порту и т. д.):
while 1:
# 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? Также неплохо было бы переносить на Linux, хотя это и не обязательно.






Вы можете посмотреть, как Pygame справляется с этим, чтобы украсть некоторые идеи.
Обработка событий PyGame работает только для графического интерфейса, а не для консоли, как попросил OP.
Стандартный подход - использовать модуль Выбрать.
Однако в Windows это не работает. Для этого вы можете использовать опрос клавиатуры модуля msvcrt.
Часто это делается с помощью нескольких потоков - по одному на каждое устройство, за которым "наблюдают", плюс фоновые процессы, которые может потребоваться прервать устройством.
Поправьте меня, если я ошибаюсь, но по моему опыту msvcrt работает только тогда, когда вы запускаете программу в командном окне, т.е. не в IDLE и не с графическим интерфейсом ...
@ digitalHamster0: все, что заменяет sys.stdin настраиваемым объектом (например, IDLE, большинство графических интерфейсов пользователя), будет иметь такой эффект. Когда sys.stdin не является настоящим файлом, вы не можете использовать select; когда он не подключен к «настоящей» консоли, вы не можете использовать функции опроса клавиатуры msvcrt (которые неявно полагаются на «настоящую» консоль).
Из комментариев:
import msvcrt # built-in module
def kbfunc():
return ord(msvcrt.getch()) if msvcrt.kbhit() else 0
Спасибо за помощь. В итоге я написал C DLL под названием PyKeyboardAccess.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;
}
И я обращаюсь к нему в python с помощью модуля ctypes (встроенного в 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 1:
x = kbfunc()
if x != 0:
print "Got key: %d" % x
else:
time.sleep(.01)
Чем это лучше встроенного msvcrt.kbhit ()? Какое у него преимущество?
Вы абсолютно правы! Я неправильно прочитал ваш пост; Я не знал, что существует модуль Python под названием msvcrt! Я просто подумал, что вы имели в виду «используйте ms crt», а потом я задумался о потоках и не стал соединять точки. Вы абсолютно правы.
Я сделал то же самое с: import msvcrt def kbfunc (): x = msvcrt.kbhit () if x: ret = ord (msvcrt.getch ()) else: ret = 0 return ret
Пожалуйста, не используйте такую лямбду. Предполагается, что «x = lambda» пишется как «def x ():» Сохранение лямбды сбивает n00bz с толку и сводит с ума опытных, пытающихся объяснить это.
РЖУ НЕ МОГУ! Это не лямбда. вот так поле "комментарии" переформатировало мою попытку вставить код в комментарий. Кстати, сохранение лямбды меня тоже смущает, и я не питон n00b :-)
Это решение меня действительно заинтриговало. Я считаю, что подход msvcrt медленный, даже в «оптимизированных» микроконтурах, как это предусмотрено многими решениями. Об этом см. связь. Хотя это решение не очень портативно.
Хорошо, поскольку моя попытка опубликовать свое решение в комментарии не удалась, вот что я пытался сказать. Я мог делать именно то, что хотел, от собственного Python (в Windows, но не где-либо еще) с помощью следующего кода:
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
не работает. получена ошибка: select.error: (10093, 'Либо приложение не вызвало WSAStartup, либо WSAStartup не удалось')
Я слышал, более чем пару раз, что системный вызов select в MS Windows не поддерживает обычные файловые дескрипторы и работает только с сокетами. (Я не знаю, работала ли когда-либо реализация select () Python под капотом).
для чего этот странный тайм-аут? У меня это работает с таймаутом = 0, но не с 0,0001, как показано.
Windows 7 + python 2.7 возвращает ошибку: i, o, e = select.select ([sys.stdin], [], [], 0) select.error: (10038, 'Попытка выполнить операцию с чем-то, что не является разъем')
Для меня это обнаруживает нажатия клавиш только после того, как я нажму Enter.
@frans: Предположительно тайм-аут существует, когда входной канал только что опустошен, но все, что подает в канал, имеет больше данных для отправки, которые не помещаются в буфер канала; вы даете процессу ввода небольшое количество времени для пополнения буфера, прежде чем отказаться.
Работает на Mac, но указывает только на нажатие клавиши ввода (или возврат введенной строки при изменении оператора return). Можно ли сразу вернуть нажатия клавиш? В сочетании, например, с code.activestate.com/recipes/134892 (который возвращает отдельные нажатия клавиш, но блоки)?
На самом деле в комментариях есть неблокирующая версия метода getch, но она пропускает нажатия клавиш, которые происходят всякий раз, когда программа выходит за пределы тайм-аута select. code.activestate.com/recipes/134892/#c12
В Ubuntu 18.10 это ничего не обнаруживает, пока не будет нажата клавиша <enter>. : - \
Решение с использованием модуля 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: Может. См. stackoverflow.com/questions/32417379/…
Были проблемы с использованием проклятий через термин SSH на безголовом хосте. Проблемы сильно испортили терминал - требовалось, чтобы он был reset между каждым запуском. Это сработало, то есть обнаружило нажатие клавиш. Должно быть более разумное решение.
Если вы объедините 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
Хотя это ничего не вернет, поэтому вместо этого вы должны использовать модуль многопроцессорного пула, который вы можете найти здесь: как получить возвращаемое значение из потока в Python?
Разве первая строка не должна быть: t = threading.Thread (target = sys.stdin.read, args = (1,))
Не будет ли это решение всегда спать в течение 5 секунд, даже если пользователь нажимает клавишу до этого?
Ни один из этих ответов не помог мне. Этот пакет, 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: Плохое отображаемое имя "".
Как упоминалось выше Дэвидом, это не лучшее решение для безголовых экземпляров, поскольку оно зависит от Xserver. import Xlib.display
Я использую это для проверки нажатий клавиш, не может быть намного проще:
#!/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, но я не мог заставить это работать
Я столкнулся с кроссплатформенной реализацией kbhit в http://home.wlu.edu/~levys/software/kbhit.py (внесены изменения для удаления нерелевантного кода):
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, пока вы это не сделаете!
Просто чтобы сообщить другим, я обнаружил, что большинство решений, связанных с библиотеками select или thread, некорректно работают в IDLE. Однако они все нормально работали на CLI, то есть
python /home/pi/poll_keyboard.py