Получение сообщения о ходе выполнения из подпроцесса

Я хочу запустить программу, для которой требуется несколько минут. В течение этого времени я хочу прочитать сообщение о ходе выполнения программы (которое выводится на стандартный вывод). Проблема в том, что я не могу найти способ считывать его вывод во время его запуска.

Единственная функция, которую я нашел для считывания вывода программы, - это Popen.communicate(), но этот метод ждет, пока процесс не завершится. Таким образом, невозможно добиться прогресса и сделать его видимым для пользователя специальным форматированным способом.

Можно ли сделать это по-другому?

Когда я запускаю процесс с помощью subprocess.popen с помощью моего script, я вижу вывод программы на экране. Можно ли скрыть это? (Ubuntu 10.10, обычный терминал)

Ответы

Ответ 1

Простейшим является вызов Popen с аргументом ключевого слова stdout=subprocess.PIPE.

p = subprocess.Popen(["ls"], stdout=subprocess.PIPE)
while True:
    line = p.stdout.readline()
    if not line:
        break
    print line

Чтобы увидеть это в действии, вот два примера скриптов. Сделайте их обоими в одном каталоге и запустите python superprint.py

printandwait.py:

import time
import sys
print 10
sys.stdout.flush()
time.sleep(10)
print 20
sys.stdout.flush()

superprint.py:

import subprocess
import sys
p = subprocess.Popen(["python printandwait.py"], shell=True, stdout=subprocess.PIPE)
while True:
    print "Looping"
    line = p.stdout.readline()
    if not line:
        break
    print line.strip()
    sys.stdout.flush()

Ответ 2

Вы можете сделать опрос о состоянии вашего подпроцесса и продолжить вывод строк.

p = subprocess.Popen('ls;sleep 10', shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)

rc = p.poll()
while rc != 0:
    while True:
        line = p.stdout.readline()
        if not line:
            break
        print line
    rc = p.poll()

assert rc == 0

Ответ 3

Это, безусловно, возможно: мой пакет python-gnupg делает именно это, создавая gpg (Gnu Privacy Guard) под подпроцессом. В общем случае вам нужно указать subprocess.PIPE для подпроцесса stdout и stderr; затем создайте два отдельных потока, которые читают подпроцесс stdout и stderr, где угодно.

В случае python-gnupg сообщения статуса из gpg считываются и действуют, пока выполняется процесс gpg (не дожидаясь завершения).

В принципе, псевдокод

process = subprocess.Popen(..., stdout=subprocess.PIPE, stderr=subprocess.PIPE)
stderr = process.stderr
rr = threading.Thread(target=response_reader_func, args=(process.stderr,))
rr.setDaemon(True)
rr.start()

dr = threading.Thread(target=data_reader_func, args=(process.stdout,))
dr.setDaemon(True)
dr.start()

dr.join()
rr.join()
process.wait()

Функции читателя - это, как правило, методы окружающего класса, которые делают правильные вещи на основе того, что они читают (в вашем случае, как-то обновляя информацию о ходе).