Subprocess.Popen.stdout - чтение stdout в режиме реального времени (снова)
Опять же, тот же вопрос.
Причина в том, что я до сих пор не могу заставить его работать после прочтения следующего:
Мое дело в том, что у меня есть консольное приложение, написанное на C, позволяет взять, например, этот код в цикле:
tmp = 0.0;
printf("\ninput>>");
scanf_s("%f",&tmp);
printf ("\ninput was: %f",tmp);
Он непрерывно считывает некоторые данные и записывает некоторые данные.
Мой код python для взаимодействия с ним следующий:
p=subprocess.Popen([path],stdout=subprocess.PIPE,stdin=subprocess.PIPE)
p.stdin.write('12345\n')
for line in p.stdout:
print(">>> " + str(line.rstrip()))
p.stdout.flush()
Пока всякий раз, когда я читаю форму p.stdout
, она всегда ждет, пока процесс не будет завершен, а затем выведет пустую строку. Я пробовал много вещей - но все тот же результат.
Я пробовал Python 2.6 и 3.1, но версия не имеет значения - мне просто нужно заставить ее работать где-то.
Ответы
Ответ 1
Попытка писать и читать из труб в подпроцесс сложна из-за буферизации по умолчанию, идущей в обоих направлениях. Крайне легко получить тупик, когда один или другой процесс (родительский или дочерний) читает из пустого буфера, записывает в полный буфер или делает блокировку чтения в буфере, ожидающем данных, до того, как системные библиотеки будут скрывать его.
Для более скромных объемов данных может быть достаточно метода Popen.communicate()
. Однако для данных, которые превышают его буферизацию, вы, вероятно, получите заторможенные процессы (аналогично тому, что вы уже видите?)
Возможно, вам захочется узнать подробности об использовании модуля fcntl
и сделать одно или другое (или оба) ваших файловых дескрипторов неблокирующими. В этом случае, конечно, вам придется обернуть все чтения и/или записи в эти дескрипторы файлов в соответствующей обработке исключений для обработки событий "EWOULDBLOCK". (Я не помню точного исключения Python, которое было создано для них).
Для вашего родителя будет использоваться совершенно другой подход к использованию модуля select
и os.fork()
... и для дочернего процесса к execve()
целевой программе после прямой обработки любого файла dup() ing. (В основном вы будете повторно реализовывать части Popen()
, но с другой обработкой дескриптора родительского файла (PIPE).
Кстати,.коммуникация, по крайней мере в стандартных библиотеках Python 2.5 и 2.6, будет обрабатывать только около 64 тыс. удаленных данных (в Linux и FreeBSD). Это число может варьироваться в зависимости от различных факторов (возможно, включая варианты построения, используемые для компиляции вашего интерпретатора Python или связанного с ним версии libc). Он не просто ограничен доступной памятью (несмотря на утверждение Ю.Ф. Себастьяна об обратном), но ограничивается гораздо меньшим значением.
Ответ 2
Нажмите считывание из трубы в отдельный поток, который сигнализирует, когда доступен кусок выхода:
Как я могу прочитать все доступные данные из подпроцесса .Popen.stdout(без блокировки)?
Ответ 3
Аргумент bufsize=256
предотвращает отправку 12345\n
дочернему процессу в блоке размером менее 256 байт, как это будет при отсутствии bufsize
или вставке p.stdin.flush()
после p.stdin.write()
. Поведение по умолчанию - это буферизация строк.
В любом случае вы должны хотя бы увидеть одну пустую строку перед блокировкой, как было выбрано первым printf(\n...)
в вашем примере.
Ответ 4
В вашем конкретном примере не требуется взаимодействие в режиме реального времени. Следующие работы:
from subprocess import Popen, PIPE
p = Popen(["./a.out"], stdin=PIPE, stdout=PIPE)
output = p.communicate(b"12345")[0] # send input/read all output
print output,
где a.out
- ваш пример программы C.
В общем, для диалогового взаимодействия с подпроцессом вы можете использовать модуль pexpect
(или его аналоги в Windows):
import pexpect
child = pexpect.spawn("./a.out")
child.expect("input>>")
child.sendline("12345.67890") # send a number
child.expect(r"\d+\.\d+") # expect the number at the end
print float(child.after) # assert that we can parse it
child.close()
Ответ 5
У меня была та же проблема, и "proc.communicate()" не решает проблему, потому что ожидает завершения процесса.
Итак, вот что работает для меня: Windows с Python 3.5.1:
import subprocess as sp
myProcess = sp.Popen( cmd, creationflags=sp.CREATE_NEW_PROCESS_GROUP,stdout=sp.PIPE,stderr=sp.STDOUT)
while i<40:
i+=1
time.sleep(.5)
out = myProcess.stdout.readline().decode("utf-8").rstrip()
Я думаю, что создание флагов и других аргументов не является обязательным (но у меня нет времени на тестирование), поэтому это будет минимальный синтаксис:
myProcess = sp.Popen( cmd, stdout=sp.PIPE)
for i in range(40)
time.sleep(.5)
out = myProcess.stdout.readline()