Подтвердить что ты не робот

Использование 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 для выполнения внешнего приложения, которое будет иметь большой выход?

4b9b3361

Ответ 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,...)

Затем вы можете прочитать файл и стереть его позже.