Захват ввода пользователем в произвольное время в 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.