Захват пользовательского ввода в произвольные моменты времени в Python - PullRequest
3 голосов
/ 17 сентября 2010

Есть ли способ отправить прерывание на модуль python, когда пользователь вводит что-то в консоль? Например, если я запускаю бесконечный цикл while, я могу окружить его попыткой / исключением KeyboardInterrupt, а затем сделать то, что мне нужно сделать в блоке исключений.

Есть ли способ дублировать эту функцию с любым произвольным вводом? Либо управляющая последовательность, либо стандартный символ?

Редактировать: Извините, это на Linux

Ответы [ 6 ]

2 голосов
/ 17 сентября 2010

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

Вот часть 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, из-за несчастья Денита с магическими значениями, которыеЯ делюсь.

2 голосов
/ 17 сентября 2010

Вам необходим отдельный процесс (или, возможно, поток), чтобы прочитать терминал и отправить его в интересующий процесс через некоторую форму межпроцессного взаимодействия (IPC) (взаимодействие между потоками может быть сложнее - в основном это единственныйчто вы делаете, это отправляете KeyboardInterrupt из вторичного потока в основной поток).Поскольку вы говорите: «Я надеялся, что будет простой способ ввести пользовательский ввод в цикл, отличный от ^ C», мне грустно разочаровывать вас, но это правда: есть способы сделать то, что вы запрашиваете, но просто это не так.

1 голос
/ 17 сентября 2010

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

1 голос
/ 17 сентября 2010

KeyboardInterrupt отличается тем, что его можно перехватить (т. Е. SIGINT в операционных системах с соответствующей поддержкой POSIX, SetConsoleCtrlHandler в Windows) и обработать соответствующим образом. Если вы хотите обрабатывать пользовательский ввод, в то же время выполняя работу в другом цикле блокировки, взгляните на модули threading или subprocess, чтобы узнать, как обмениваться данными между двумя различными потоками / процессами. Также не забудьте получить представление о проблемах, которые неразрывно связаны с параллельными вычислениями (например, условия гонки, безопасность / синхронизация потоков и т. Д.)

0 голосов
/ 13 июля 2013

РЕДАКТИРОВАТЬ: Гвидо отредактировал для меня принятый ответ, поэтому этот ответ больше не нужен.

Принятый ответ больше не работает из-за изменений, сделанных Мухаммедом. Я попытался внести исправление, но оно продолжает отклоняться, поэтому я опубликую его как отдельный ответ. Мой код почти идентичен его, только 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.

0 голосов
/ 18 сентября 2010

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


while True:
        try:
            time.sleep( 1 )
            #do stuff
        except KeyboardInterrupt:
            inp = raw_input( '>' )
            if inp == 'i':
                obj.integrate()
            elif inp == 'r':
                obj.reset()
...