Использование subprocess.Popen для процесса с большим выходом
У меня есть код Python, который выполняет внешнее приложение, которое отлично работает, когда приложение имеет небольшой объем вывода, но зависает, когда есть много. Мой код выглядит так:
p = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
errcode = p.wait()
retval = p.stdout.read()
errmess = p.stderr.read()
if errcode:
log.error('cmd failed <%s>: %s' % (errcode,errmess))
В документах есть комментарии, которые, как представляется, указывают на потенциальную проблему. В ожидании есть:
Предупреждение. Это будет заторможен, если дочерний процесс генерирует достаточный вывод в канал stdout
или stderr
, так что он блокирует ожидание того, что буфер буфера ОС будет принимать больше данных. Используйте communicate()
, чтобы избежать этого.
хотя я общаюсь, я вижу:
Примечание. Чтение данных буферизуется в памяти, поэтому не используйте этот метод, если размер данных является большим или неограниченным.
Поэтому мне непонятно, что я должен использовать любой из них, если у меня есть большой объем данных. Они не указывают, какой метод я должен использовать в этом случае.
Мне нужно вернуть значение из exec и выполнить синтаксический анализ и использовать как stdout
, так и stderr
.
Итак, что такое эквивалентный метод в Python для выполнения внешнего приложения, которое будет иметь большой выход?
Ответы
Ответ 1
Вы блокируете чтение двух файлов; первый должен завершиться до начала второго запуска. Если приложение много пишет для stderr
, а ничего для stdout
, то ваш процесс будет сидеть в ожидании данных на stdout
, которые не наступают, а программа, в которой вы работаете, сидит там, ожидая, что это будет написано на stderr
для чтения (чего никогда не будет - с тех пор, как вы ждете stdout
).
Есть несколько способов исправить это.
Простейшим является не перехват stderr
; оставьте stderr=None
. Ошибки будут выводиться непосредственно на stderr
. Вы не можете перехватить их и отобразить их как часть своего собственного сообщения. Для инструментов командной строки это часто бывает нормально. Для других приложений это может быть проблемой.
Другим простым подходом является перенаправление stderr
на stdout
, поэтому у вас есть только один входящий файл: set stderr=STDOUT
. Это означает, что вы не можете отличить регулярный вывод от вывода ошибки. Это может быть или не быть приемлемым, в зависимости от того, как приложение записывает вывод.
Полный и сложный способ обработки: select
(http://docs.python.org/library/select.html). Это позволяет вам читать без блокировки: вы получаете данные всякий раз, когда данные появляются либо на stdout
, либо stderr
. Я бы рекомендовал это, если это действительно необходимо. Вероятно, это не работает в Windows.
Ответ 2
Много выходных данных субъективно, поэтому немного сложно сделать рекомендацию. Если объем вывода действительно большой, вы, скорее всего, не захотите его захватить с помощью единого вызова read(). Вы можете попробовать записать вывод в файл, а затем потянуть данные пошагово так:
f=file('data.out','w')
p = subprocess.Popen(cmd, shell=True, stdout=f, stderr=subprocess.PIPE)
errcode = p.wait()
f.close()
if errcode:
errmess = p.stderr.read()
log.error('cmd failed <%s>: %s' % (errcode,errmess))
for line in file('data.out'):
#do something
Ответ 3
Гленн Мейнард прав в своих комментариях о тупиках. Однако наилучшим способом решения этой проблемы является создание двух потоков, один для stdout и один для stderr, которые считывают эти соответствующие потоки до исчерпания и делают все, что вам нужно, с выходом.
Предложение использования временных файлов может работать или не работать для вас в зависимости от размера вывода и т.д. и нужно ли обрабатывать вывод подпроцесса по мере его создания.
Как предложил Хейкки Тойвонен, вы должны посмотреть на метод communicate
. Тем не менее, это буферизирует stdout/stderr подпроцесса в памяти, и вы получаете возвращаемые из вызова communicate
- это не идеально для некоторых сценариев. Но источник метода связи стоит посмотреть.
Другой пример - в пакете, который я поддерживаю, python-gnupg, где исполняемый файл gpg
создается с помощью subprocess
, чтобы выполнить тяжелая работа, а оболочка Python запускает потоки для чтения gpg stdout и stderr и потребляет их, поскольку данные создаются gpg. Вы можете получить некоторые идеи, посмотрев на источник там. Данные, полученные gpg как для stdout, так и для stderr, могут быть довольно большими в общем случае.
Ответ 4
Чтение stdout
и stderr
независимо с очень большим выходом (т.е. большим количеством мегабайт) с помощью select
:
import subprocess, select
proc = subprocess.Popen(cmd, bufsize=8192, shell=False, \
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
with open(outpath, "wb") as outf:
dataend = False
while (proc.returncode is None) or (not dataend):
proc.poll()
dataend = False
ready = select.select([proc.stdout, proc.stderr], [], [], 1.0)
if proc.stderr in ready[0]:
data = proc.stderr.read(1024)
if len(data) > 0:
handle_stderr_data(data)
if proc.stdout in ready[0]:
data = proc.stdout.read(1024)
if len(data) == 0: # Read of zero bytes means EOF
dataend = True
else:
outf.write(data)
Ответ 5
Вы можете попробовать общаться и посмотреть, решит ли это вашу проблему. Если нет, я перенаправил вывод во временный файл.
Ответ 6
У меня была та же проблема. Если вам нужно обработать большой вывод, другим хорошим вариантом может быть использование файла для stdout и stderr и передача этих файлов для каждого параметра.
Проверьте модуль tempfile в python: https://docs.python.org/2/library/tempfile.html.
Что-то вроде этого может работать
out = tempfile.NamedTemporaryFile(delete=False)
Тогда вы бы сделали:
Popen(... stdout=out,...)
Затем вы можете прочитать файл и стереть его позже.