Множество входов и выходов в подпроцессе 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 делает. Как я могу получить вывод для каждого слова, по одному за раз?

5 ответов

Решение

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

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

import pexpect

analyzer = pexpect.spawnu('hfst-lookup analyser-gt-desc.hfstol')
analyzer.expect('> ')

for word in ['слово', 'сработай']:
    print('Trying', word, '...')
    analyzer.sendline(word)
    analyzer.expect('> ')
    print(analyzer.before)

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

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

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

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

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

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

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

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

Записать одну строку в стандартный ввод, прочитать одну строку из стандартного вывода, выполнить цикл.

На основе этого примера Устада Эли :

main.py

      from subprocess import Popen, PIPE, STDOUT
import time

p = Popen(['./upline.py'], stdout=PIPE, stdin=PIPE)

p.stdin.write('Hello world\n'.encode())
p.stdin.flush()
print(p.stdout.readline().decode()[:-1])

time.sleep(1)

p.stdin.write('bonne journeé\n'.encode())
p.stdin.flush()
print(p.stdout.readline().decode()[:-1])

time.sleep(1)

p.stdin.write('goodbye world\n'.encode())
p.stdin.flush()
print(p.stdout.readline().decode()[:-1])

upline.py

      #!/usr/bin/env python

import sys

for line in sys.stdin:
    print(line.upper(), end='', flush=True)

Выход:

      HELLO WORLD
BONNE JOURNEÉ
GOODBYE WORLD

HELLO WORLDпоявляется мгновенно, затем ждет одну секунду, затемBONNE JOURNEÉ, затем еще секунду спустяGOODBYE WORLD.

Протестировано на Python 3.11.4, Ubuntu 23.04.

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