Опрос клавиатуры (обнаружение нажатия клавиши) в Python

Как я могу опросить клавиатуру из консольного приложения 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, хотя это и не обязательно.

Просто чтобы сообщить другим, я обнаружил, что большинство решений, связанных с библиотеками select или thread, некорректно работают в IDLE. Однако они все нормально работали на CLI, то есть python /home/pi/poll_keyboard.py

davidhood2 19.10.2016 14:18
Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
69
1
135 407
10
Перейти к ответу Данный вопрос помечен как решенный

Ответы 10

Вы можете посмотреть, как Pygame справляется с этим, чтобы украсть некоторые идеи.

Обработка событий PyGame работает только для графического интерфейса, а не для консоли, как попросил OP.

Ricardo Cruz 07.12.2018 02:53
Ответ принят как подходящий

Стандартный подход - использовать модуль Выбрать.

Однако в Windows это не работает. Для этого вы можете использовать опрос клавиатуры модуля msvcrt.

Часто это делается с помощью нескольких потоков - по одному на каждое устройство, за которым "наблюдают", плюс фоновые процессы, которые может потребоваться прервать устройством.

Поправьте меня, если я ошибаюсь, но по моему опыту msvcrt работает только тогда, когда вы запускаете программу в командном окне, т.е. не в IDLE и не с графическим интерфейсом ...

DarthVlader 24.06.2018 11:14

@ digitalHamster0: все, что заменяет sys.stdin настраиваемым объектом (например, IDLE, большинство графических интерфейсов пользователя), будет иметь такой эффект. Когда sys.stdin не является настоящим файлом, вы не можете использовать select; когда он не подключен к «настоящей» консоли, вы не можете использовать функции опроса клавиатуры msvcrt (которые неявно полагаются на «настоящую» консоль).

ShadowRanger 29.08.2018 18:52

Из комментариев:

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 ()? Какое у него преимущество?

S.Lott 16.11.2008 06:33

Вы абсолютно правы! Я неправильно прочитал ваш пост; Я не знал, что существует модуль Python под названием msvcrt! Я просто подумал, что вы имели в виду «используйте ms crt», а потом я задумался о потоках и не стал соединять точки. Вы абсолютно правы.

K. Brafford 16.11.2008 07:33

Я сделал то же самое с: import msvcrt def kbfunc (): x = msvcrt.kbhit () if x: ret = ord (msvcrt.getch ()) else: ret = 0 return ret

K. Brafford 16.11.2008 07:34

Пожалуйста, не используйте такую ​​лямбду. Предполагается, что «x = lambda» пишется как «def x ():» Сохранение лямбды сбивает n00bz с толку и сводит с ума опытных, пытающихся объяснить это.

S.Lott 16.11.2008 16:02

РЖУ НЕ МОГУ! Это не лямбда. вот так поле "комментарии" переформатировало мою попытку вставить код в комментарий. Кстати, сохранение лямбды меня тоже смущает, и я не питон n00b :-)

K. Brafford 20.11.2008 03:28

Это решение меня действительно заинтриговало. Я считаю, что подход msvcrt медленный, даже в «оптимизированных» микроконтурах, как это предусмотрено многими решениями. Об этом см. связь. Хотя это решение не очень портативно.

DevPlayer 30.06.2015 23:20

Хорошо, поскольку моя попытка опубликовать свое решение в комментарии не удалась, вот что я пытался сказать. Я мог делать именно то, что хотел, от собственного 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 не удалось')

DarenW 18.12.2012 03:17

Я слышал, более чем пару раз, что системный вызов select в MS Windows не поддерживает обычные файловые дескрипторы и работает только с сокетами. (Я не знаю, работала ли когда-либо реализация select () Python под капотом).

Jim Dennis 13.07.2013 23:54

для чего этот странный тайм-аут? У меня это работает с таймаутом = 0, но не с 0,0001, как показано.

frans 17.03.2016 18:47

Windows 7 + python 2.7 возвращает ошибку: i, o, e = select.select ([sys.stdin], [], [], 0) select.error: (10038, 'Попытка выполнить операцию с чем-то, что не является разъем')

Vit Bernatik 25.10.2016 19:25

Для меня это обнаруживает нажатия клавиш только после того, как я нажму Enter.

Mark Smith 13.07.2017 18:50

@frans: Предположительно тайм-аут существует, когда входной канал только что опустошен, но все, что подает в канал, имеет больше данных для отправки, которые не помещаются в буфер канала; вы даете процессу ввода небольшое количество времени для пополнения буфера, прежде чем отказаться.

ShadowRanger 29.08.2018 18:54

Работает на Mac, но указывает только на нажатие клавиши ввода (или возврат введенной строки при изменении оператора return). Можно ли сразу вернуть нажатия клавиш? В сочетании, например, с code.activestate.com/recipes/134892 (который возвращает отдельные нажатия клавиш, но блоки)?

Sampo 26.10.2018 21:12

На самом деле в комментариях есть неблокирующая версия метода getch, но она пропускает нажатия клавиш, которые происходят всякий раз, когда программа выходит за пределы тайм-аута select. code.activestate.com/recipes/134892/#c12

Sampo 26.10.2018 21:21

В Ubuntu 18.10 это ничего не обнаруживает, пока не будет нажата клавиша <enter>. : - \

Jonathan Hartley 18.03.2019 22:45

Решение с использованием модуля 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/…

Joshua Clayton 25.05.2016 21:47

Были проблемы с использованием проклятий через термин SSH на безголовом хосте. Проблемы сильно испортили терминал - требовалось, чтобы он был reset между каждым запуском. Это сработало, то есть обнаружило нажатие клавиш. Должно быть более разумное решение.

Mark 01.01.2018 00:31

Если вы объедините 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,))

K. Brafford 17.10.2015 08:06

Не будет ли это решение всегда спать в течение 5 секунд, даже если пользователь нажимает клавишу до этого?

K. Brafford 17.10.2015 08:08

Ни один из этих ответов не помог мне. Этот пакет, 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 она работает нормально.

Trevor 06.09.2017 05:35

Это решение не работает, когда скрипт запускается через ssh. Он вылетает с ошибкой: 'Xlib.error.DisplayNameError: Плохое отображаемое имя "".

David Stein 19.11.2017 07:05

Как упоминалось выше Дэвидом, это не лучшее решение для безголовых экземпляров, поскольку оно зависит от Xserver. import Xlib.display

Mark 01.01.2018 00:29

Я использую это для проверки нажатий клавиш, не может быть намного проще:

#!/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, пока вы это не сделаете!

Другие вопросы по теме