Несколько входов и выходов в подпроцессе python общаются

Мне нужно сделать что-то вроде этот пост, но мне нужно создать подпроцесс, который может много раз получать входные данные и выдавать выходные данные. Принятый ответ на этот пост имеет хороший код...

from subprocess import Popen, PIPE, STDOUT

p = Popen(['grep', 'f'], stdout=PIPE, stdin=PIPE, stderr=STDOUT)    
grep_stdout = p.communicate(input=b'one\ntwo\nthree\nfour\nfive\nsix\n')[0]
print(grep_stdout.decode())

# four
# five

...что я хотел бы продолжить так:

grep_stdout2 = p.communicate(input=b'spam\neggs\nfrench fries\nbacon\nspam\nspam\n')[0]
print(grep_stdout2.decode())

# french fries

Но, увы, я получаю следующую ошибку:

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/opt/local/Library/Frameworks/Python.framework/Versions/3.4/lib/python3.4/subprocess.py", line 928, in communicate
    raise ValueError("Cannot send input after starting communication")
ValueError: Cannot send input after starting communication

Метод proc.stdin.write() не позволяет вам собирать вывод, если я правильно понимаю. Каков самый простой способ сохранить линии открытыми для текущего ввода/вывода?

Изменить: ====================

Похоже, что pexpect — полезная библиотека для того, что я пытаюсь сделать, но у меня возникли проблемы с ее работой. Вот более полное объяснение моей реальной задачи. Я использую hfst для анализа грамматики отдельных (русских) слов. Следующее демонстрирует его поведение в оболочке bash:

$ hfst-lookup analyser-gt-desc.hfstol
> слово
слово   слово+N+Neu+Inan+Sg+Acc 0.000000
слово   слово+N+Neu+Inan+Sg+Nom 0.000000

> сработай
сработай    сработать+V+Perf+IV+Imp+Sg2 0.000000
сработай    сработать+V+Perf+TV+Imp+Sg2 0.000000

> 

Я хочу, чтобы мой скрипт мог получать анализ одной формы за раз. Я пробовал такой код, но он не работает.

import pexpect

analyzer = pexpect.spawnu('hfst-lookup analyser-gt-desc.hfstol')
for newWord in ['слово','сработай'] :
    print('Trying', newWord, '...')
    analyzer.expect('> ')
    analyzer.sendline( newWord )
    print(analyzer.before)

# trying слово ...
# 
# trying сработай ...
# слово
# слово слово+N+Neu+Inan+Sg+Acc 0.000000
# слово слово+N+Neu+Inan+Sg+Nom 0.000000
# 
# 

Очевидно, я неправильно понял, что делает pexpect.before. Как я могу получить вывод для каждого слова по одному?


person reynoldsnlp    schedule 19.02.2015    source источник
comment
Метод proc.stdin.write() не позволяет вам собирать выходные данные. Вы все равно можете получить выходные данные, вам просто нужно получить их из proc.stdout и proc.stderr.   -  person Joshua Taylor    schedule 19.02.2015
comment
Это винда или линукс? В Linux модуль pexpect является хорошим выбором для взаимодействия подпроцессов.   -  person tdelaney    schedule 19.02.2015
comment
Что ты пытаешься сделать?   -  person Padraic Cunningham    schedule 19.02.2015
comment
обязательное чтение, если вы хотите несколько входных и выходных данных: В: Почему бы просто не использовать канал (открыть())?   -  person jfs    schedule 20.02.2015
comment
@PadraicCunningham Я пытаюсь получить пример доказательства концепции, который я могу расширить для использования в сценарии, который должен будет взаимодействовать с моим процессом более 1 000 000 раз. Мой текущий скрипт использует subprocess.check_output, что слишком медленно, так как ему приходится инициировать процесс для каждого взаимодействия.   -  person reynoldsnlp    schedule 20.02.2015
comment
@tdelaney Это на OS X.   -  person reynoldsnlp    schedule 20.02.2015
comment
последовательность должна быть такой: 0. дождаться первого приглашения 1. отправить слово 2. дождаться приглашения, получить ответ (.after?) на слово 3. повторить 1-2   -  person jfs    schedule 20.02.2015


Ответы (4)


Popen.communicate() — это вспомогательный метод, который выполняет однократную запись данных в stdin и создает потоки для извлечения данных из stdout и stderr. Он закрывает stdin после завершения записи данных и считывает stdout и stderr до тех пор, пока эти каналы не закроются. Вы не можете сделать второй communicate, потому что ребенок уже вышел к тому времени, когда он возвращается.

Интерактивный сеанс с дочерним процессом немного сложнее.

Одна из проблем заключается в том, распознает ли дочерний процесс, что он должен быть интерактивным. В библиотеках C, которые большинство программ командной строки используют для взаимодействия, программы, запускаемые из терминалов (например, консоли linux или псевдотерминала «pty»), являются интерактивными и часто сбрасывают свой вывод, но те, которые запускаются из других программ через PIPES, не являются интерактивными. интерактивны и редко очищают свой вывод.

Другой вопрос, как вы должны читать и обрабатывать stdout и stderr без взаимной блокировки. Например, если вы заблокируете чтение stdout, но stderr заполнит свою трубу, потомок остановится, и вы застрянете. Вы можете использовать потоки для извлечения обоих во внутренние буферы.

Еще один способ — это то, как вы справляетесь с ребенком, который неожиданно уходит.

Для систем «unixy», таких как linux и OSX, модуль pexpect написан для обработки сложностей интерактивного дочернего процесса. Для Windows я не знаю хорошего инструмента для этого.

person tdelaney    schedule 19.02.2015
comment
вы можете зайти в тупик даже с помощью только stdin/stdout, например, как узнать, когда читать из stdout grep после того, как вы что-то написали? Кроме того, дочерний процесс может полностью обойти stdin/stdout (типичный пример: запрос пароля). См. ссылку в комментарии выше. Во многих случаях эти проблемы можно решить с помощью потоков, fcntl, async.io: select/poll/epoll/kqueue/iocp и/или pty. И иногда достаточно быть осторожным - person jfs; 20.02.2015
comment
@ J.F.Sebastian - а еще есть программы, которые считывают тип терминала для раскрашивания или полноэкранного вывода. Это может быть проблемой. - person tdelaney; 20.02.2015
comment
если программа не предоставляет параметры для переопределения такого поведения (например, --color); это можно считать ошибкой. Поведение по умолчанию должно быть для интерактивного пользователя — меньше печатать, краткий вывод. Полноэкранный режим не следует использовать, если в этом нет крайней необходимости. - person jfs; 20.02.2015

Этот ответ следует отнести к @JFSebastian. Спасибо за комментарии!

Следующий код получил мое ожидаемое поведение:

import pexpect

analyzer = pexpect.spawn('hfst-lookup analyser-gt-desc.hfstol', encoding='utf-8')
analyzer.expect('> ')

for word in ['слово', 'сработай']:
    print('Trying', word, '...')
    analyzer.sendline(word)
    analyzer.expect('> ')
    print(analyzer.before)
person reynoldsnlp    schedule 24.02.2015
comment
@Mooncrater похоже, что pexpect.spawnu устарело в пользу использования spawn(encoding='utf-8'). Я обновил ответ соответственно. Однако он все еще находится в исходном коде (github. com/pexpect/pexpect/blob/master/pexpect/), поэтому мне интересно, правильно ли вы установили pexpect. - person reynoldsnlp; 28.07.2020
comment
Ожидайте хорошей работы для имитации взаимодействия с терминалом для программ, которые изменят свое поведение, когда они находятся в конвейере. Однако это не хорошо, если вы общаетесь с двоичными данными. - person Camion; 15.07.2021
comment
@Camion для двоичных данных, просто опустите аргумент encoding='utf-8'. По умолчанию spawn ожидает двоичный файл. - person reynoldsnlp; 15.07.2021
comment
Я не думаю, что это так просто из-за обработки новых строк. Я не проверял это прямо сейчас, но я считаю, что даже без кодировки pexpect преобразует символы конца строки в окна, такие как конец строки (\r\n), и у вас также могут возникнуть проблемы, если конец ваших данных не новый линия. - person Camion; 15.07.2021

Всякий раз, когда вы хотите отправить входные данные процессу, используйте proc.stdin.write(). Всякий раз, когда вы хотите получить результат процесса, используйте proc.stdout.read(). Оба аргумента stdin и stdout конструктора должны быть установлены в PIPE.

person Tom Hunt    schedule 19.02.2015
comment
Это работает отлично. Процедура принимает один ввод и отправляет один вывод. - person dom free; 23.03.2018
comment
Это следует использовать с осторожностью с разделителями строк и сбросом PIPE, так как это может привести к взаимоблокировкам. Больше информации можно найти в этом замечательном сообщении в блоге: eli.thegreenplace.net/2017/ - person Maged Saeed; 14.05.2020

HFST имеет привязки к Python: https://pypi.python.org/pypi/hfst

Их использование должно избежать всей проблемы с очисткой и даст вам более чистый API для работы, чем синтаксический анализ строкового вывода из pexpect.

Из Python REPL вы можете получить некоторые документы по привязкам с помощью

dir(hfst)
help(hfst.HfstTransducer)

или прочитайте https://hfst.github.io/python/3.12.2/QuickStart.html

Выхватывание соответствующих частей документов:

istr = hfst.HfstInputStream('hfst-lookup analyser-gt-desc.hfstol')
transducers = []
while not (istr.is_eof()):
    transducers.append(istr.read())
istr.close()
print("Read %i transducers in total." % len(transducers))
if len(transducers) == 1:
  out = transducers[0].lookup_optimize("слово")
  print("got %s" % (out,))
else: 
  pass # or handle >1 fst in the file, though I'm guessing you don't use that feature
person unhammer    schedule 22.11.2017