Подпроцесс программы Python C зависает в строке "for line in iter"

Хорошо, поэтому я пытаюсь запустить программу на C с помощью python script. В настоящее время я использую программу тестирования C:

#include <stdio.h>

int main() {
while (1) {
    printf("2000\n");
    sleep(1);
}
return 0;
}

Для имитации программы, которую я буду использовать, которая постоянно считывает показания с датчика. Затем я пытаюсь прочитать вывод (в данном случае "2000") из программы C с подпроцессом в python:

#!usr/bin/python
import subprocess

process = subprocess.Popen("./main", stdout=subprocess.PIPE)
while True:
    for line in iter(process.stdout.readline, ''):
            print line,

но это не работает. Из использования операторов печати он запускает строку .Popen, а затем ждет for line in iter(process.stdout.readline, ''):, пока не нажму Ctrl-C.

Почему это? Это именно то, что большинство примеров, которые я видел, имеют в качестве своего кода, и все же он не читает файл.

Edit:

Есть ли способ заставить его работать только тогда, когда есть что-то, что нужно прочитать?

Ответы

Ответ 1

Это проблема с блочной буферизацией.

Далее следует расширенная для вашего варианта версия моего ответа на Python: прочитайте потоковый ввод из вопроса subprocess.communicate().

Исправить буфер stdout непосредственно в программе C

stdio основанные программы, как правило, буферизируются в строке, если они работают интерактивно в терминале и блокируются буфером, когда их stdout перенаправляется на канал. В последнем случае вы не увидите новые строки до тех пор, пока буфер не переполнится или не будет сброшен.

Чтобы избежать вызова fflush() после каждого вызова printf(), вы можете принудительно выполнить вывод строки с буферизацией, вызывая вначале программу C:

setvbuf(stdout, (char *) NULL, _IOLBF, 0); /* make line buffered stdout */

Как только печатается новая строка, буфер в этом случае очищается.

Или исправить его без изменения источника программы C

Существует утилита stdbuf, которая позволяет изменять тип буферизации без изменения исходного кода, например:

from subprocess import Popen, PIPE

process = Popen(["stdbuf", "-oL", "./main"], stdout=PIPE, bufsize=1)
for line in iter(process.stdout.readline, b''):
    print line,
process.communicate() # close process' stream, wait for it to exit

Существуют и другие утилиты, см. Отключение буферизации в канале.

Или используйте pseudo-TTY

Чтобы обмануть подпроцесс, подумав, что он работает в интерактивном режиме, вы можете использовать pexpect module или его аналоги, для примеров кода, которые используйте модули pexpect и pty, см. Подпроцессы подпроцесса Python() висят. Здесь приведен пример варианта pty (он должен работать на Linux):

#!/usr/bin/env python
import os
import pty
import sys
from select import select
from subprocess import Popen, STDOUT

master_fd, slave_fd = pty.openpty()  # provide tty to enable line buffering
process = Popen("./main", stdin=slave_fd, stdout=slave_fd, stderr=STDOUT,
                bufsize=0, close_fds=True)
timeout = .1 # ugly but otherwise `select` blocks on process' exit
# code is similar to _copy() from pty.py
with os.fdopen(master_fd, 'r+b', 0) as master:
    input_fds = [master, sys.stdin]
    while True:
        fds = select(input_fds, [], [], timeout)[0]
        if master in fds: # subprocess' output is ready
            data = os.read(master_fd, 512) # <-- doesn't block, may return less
            if not data: # EOF
                input_fds.remove(master)
            else:
                os.write(sys.stdout.fileno(), data) # copy to our stdout
        if sys.stdin in fds: # got user input
            data = os.read(sys.stdin.fileno(), 512)
            if not data:
                input_fds.remove(sys.stdin)
            else:
                master.write(data) # copy it to subprocess' stdin
        if not fds: # timeout in select()
            if process.poll() is not None: # subprocess ended
                # and no output is buffered <-- timeout + dead subprocess
                assert not select([master], [], [], 0)[0] # race is possible
                os.close(slave_fd) # subproces don't need it anymore
                break
rc = process.wait()
print("subprocess exited with status %d" % rc)

Или используйте pty через pexpect

pexpect завершает обработку pty в интерфейс более высокого уровня:

#!/usr/bin/env python
import pexpect

child = pexpect.spawn("/.main")
for line in child:
    print line,
child.close()

Q: Почему бы просто не использовать pipe (popen())? объясняет, почему полезен псевдотематик.

Ответ 2

Ваша программа не висит, она работает очень медленно. Ваша программа использует буферизованный вывод; данные "2000\n" не записываются в stdout немедленно, но в конечном итоге это сделает. В вашем случае для завершения может потребоваться BUFSIZ/strlen("2000\n") секунды (возможно, 1638 секунд).

После этой строки:

printf("2000\n");

добавить

fflush(stdout);

Ответ 3

Смотрите документы readline.

Ваш код:

process.stdout.readline

Ожидает EOF или новую строку.

Я не могу сказать, что вы в конечном счете пытаетесь сделать, но добавление новой строки для вашего printf, например, printf("2000\n");, должно по крайней мере начать работу.