Захват ввода пользователем в произвольное время в python
Есть ли способ отправить прерывание в модуль python, когда пользователь вводит что-то в консоль? Например, если я запускаю бесконечный цикл while, я могу окружить его с помощью try/except KeyboardInterrupt, а затем делать то, что мне нужно сделать в блоке except.
Есть ли способ дублировать эту функцию с любым произвольным вводом? Либо управляющая последовательность, либо стандартный символ?
Изменить: Извините, это на linux
Ответы
Ответ 1
В зависимости от операционной системы и доступных библиотек существуют разные способы ее достижения. Этот ответ предоставляет некоторые из них.
Вот часть Linux/OS X, скопированная оттуда, с бесконечным циклом, завершенным с использованием escape-символа. Для решения Windows вы можете сами проверить ответ.
import sys
import select
import tty
import termios
from curses import ascii
def isData():
return select.select([sys.stdin], [], [], 0) == ([sys.stdin], [], [])
old_settings = termios.tcgetattr(sys.stdin)
try:
tty.setcbreak(sys.stdin.fileno())
i = 0
while 1:
print i
i += 1
if isData():
c = sys.stdin.read(1)
if c == chr(ascii.ESC):
break
finally:
termios.tcsetattr(sys.stdin, termios.TCSADRAIN, old_settings)
Изменить. Изменено распознавание символов для использования символов, как определено curses.ascii
, благодаря несчастью Daenyth с магическими значениями, которые я разделяю.
Ответ 2
Вам нужен отдельный процесс (или, возможно, поток), чтобы прочитать терминал и отправить его в интересующий процесс через некоторую форму межпроцессного взаимодействия (IPC) (межпоточная связь может быть сложнее - в основном единственная вещь, которую вы делаете, отправляет KeyboardInterrupt
из вторичного потока в основной поток). Поскольку вы говорите: "Я надеялся, что будет простой способ ввести пользовательский ввод в цикл, отличный от" C ", мне грустно разочаровывать вас, но это правда: есть способы делать то, что вы запрашиваете, но просто они не являются.
Ответ 3
KeyboardInterrupt отличается тем, что он может быть захвачен (т.е. SIGINT в операционных системах с соответствующей поддержкой POSIX, SetConsoleCtrlHandler в Windows) и обрабатывается соответственно. Если вы хотите обработать пользовательский ввод и в то же время выполнять работу в цикле блокировки в противном случае, посмотрите модули threading
или subprocess
, чтобы посмотреть, как обмениваться данными между двумя различными потоками/процессами. Также не забудьте понять проблемы, которые идут рука об руку с параллельными вычислениями (например, условия гонки, безопасность/синхронизация потоков и т.д.).
Ответ 4
Я не уверен, что это наиболее оптимальное решение, но вы можете создать поток, который выполняет while True: sys.stdin.read(1)
Таким образом, вы всегда можете прочитать все входные данные, но он будет медленным, и вам придется комбинировать строки самостоятельно.
Пример:
import os
import sys
import time
import threading
class StdinReader(threading.Thread):
def run(self):
while True:
print repr(sys.stdin.read(1))
os.system('stty raw')
stdin_reader = StdinReader()
stdin_reader.start()
while True:
time.sleep(1)
os.system('stty sane')
stty raw
вещь зависит от того, где вы ее запускаете. Это не будет работать везде.
Ответ 5
Вот как я реализовал функциональность, которую я хотел, но это НЕ ТОЧНО, о чем мой вопрос. Поэтому я опубликую это на случай, если кто-то ищет ту же концепцию, но примите один из более прямых ответов.
while True:
try:
time.sleep( 1 )
#do stuff
except KeyboardInterrupt:
inp = raw_input( '>' )
if inp == 'i':
obj.integrate()
elif inp == 'r':
obj.reset()
Ответ 6
EDIT: Guido сделал редактирование для меня на принятом ответе, поэтому этот ответ больше не нужен.
Принятый ответ больше не работает из-за изменения, сделанного Мухаммедом. Я попытался подать исправление, но он продолжает отклоняться, поэтому я отправлю его как отдельный ответ. Мой код почти идентичен его, всего лишь 1 крошечное изменение:
import sys
import select
import tty
import termios
from curses import ascii
def isData():
return select.select([sys.stdin], [], [], 0) == ([sys.stdin], [], [])
old_settings = termios.tcgetattr(sys.stdin)
try:
tty.setcbreak(sys.stdin.fileno())
i = 0
while 1:
print i
i += 1
if isData():
c = sys.stdin.read(1)
if c == chr(ascii.ESC):
break
finally:
termios.tcsetattr(sys.stdin, termios.TCSADRAIN, old_settings)
Единственное различие заключается в том, что вместо "c == ascii.ESC" я изменил его на "c == chr (ascii.ESC). Я и еще один разработчик оба протестировали и подтвердили, что это изменение необходимо, и что в противном случае программа будет работать некорректно.
Программа должна показывать большие и большие числа, пока вы не нажмете ESC, а затем выйдите. Но без chr() вокруг ascii.ESC он не обнаружит нажатия ESC.