Korzystanie z podprocesu.Popen dla procesu o dużej wydajności

Mam trochę kodu Pythona, który wykonuje zewnętrzną aplikację, która działa dobrze, gdy aplikacja ma niewielką ilość danych wyjściowych, ale zawiesza się, gdy jest ich dużo. Mój kod wygląda następująco:

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))

W dokumentach są komentarze, które wydają się wskazywać na potencjalny problem. Pod waitem jest:

Ostrzeżenie: spowoduje to zablokowanie, jeśli proces potomny wygeneruje wystarczającą ilość danych wyjściowych do potoku stdout lub stderr, tak że blokuje oczekiwanie na akceptację większej ilości danych przez bufor potoku systemu operacyjnego. Użyj communicate(), aby uniknąć to.

Choć pod komunikatem widzę:

Uwaga odczytywane dane są buforowane w pamięci, więc nie używaj tej metody, jeśli rozmiar danych jest duży lub nieograniczony.

Więc nie jest dla mnie jasne, że powinienem użyć któregokolwiek z nich, jeśli mam dużą ilość danych. Nie wskazują, jaką metodę powinienem zastosować w tym przypadku.

Potrzebuję zwracanej wartości z exec i analizuję i używam zarówno stdout jak i stderr.

Więc jaka jest metoda równoważna w Pythonie do exec zewnętrzna aplikacja, która będzie miała duże wyjście?

Author: SilentGhost, 2009-07-25

7 answers

Blokujesz odczyt dwóch plików; pierwszy musi zostać ukończony przed uruchomieniem drugiego. Jeśli aplikacja dużo pisze do stderr, a nic do stdout, wtedy twój proces będzie czekał na dane na stdout, które nie nadchodzą, podczas gdy uruchomiony program czeka na to, co napisał do stderr, aby zostać odczytanym(co nigdy nie będzie-ponieważ czekasz na stdout).

Jest kilka sposobów, aby to naprawić.

Najprostszym jest nie przechwytywanie stderr; zostawić Błędy będą wyświetlane bezpośrednio do stderr. Nie możesz ich przechwycić i wyświetlić jako część własnej wiadomości. W przypadku narzędzi wiersza poleceń jest to często OK. W przypadku innych aplikacji może to być problem.

Innym prostym podejściem jest przekierowanie stderr do stdout, więc masz tylko jeden plik przychodzący: set stderr=STDOUT. Oznacza to, że nie można odróżnić zwykłego wyjścia od wyjścia błędu. Może to być dopuszczalne lub nie, w zależności od tego, jak aplikacja zapisuje dane wyjściowe.

The kompletny i skomplikowany sposób postępowania z tym jest select (http://docs.python.org/library/select.html ). pozwala to odczytywać dane w sposób nieblokujący: otrzymujesz dane za każdym razem, gdy dane pojawiają się na stdout lub stderr. Polecam to tylko wtedy, gdy jest to naprawdę konieczne. To prawdopodobnie nie działa w Windows.

 16
Author: Glenn Maynard,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-09-05 10:12:25

wiele wyników jest subiektywnych, więc trochę trudno jest wydać rekomendację. Jeśli wielkość wyjścia jest naprawdę duża, prawdopodobnie nie chcesz przechwytywać wszystkiego jednym wywołaniem read (). Możesz spróbować zapisać wyjście do pliku, a następnie pobrać dane stopniowo w taki sposób:

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
 6
Author: Mark Roddy,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-07-24 23:18:34

Glenn Maynard ma rację w swoim komentarzu o impasach. Jednak najlepszym sposobem rozwiązania tego problemu jest utworzenie dwóch wątków, jednego dla stdout i jednego dla stderr, które odczytują odpowiednie strumienie do wyczerpania i robią wszystko, czego potrzebujesz z wyjściem.

Sugestia użycia plików tymczasowych może, ale nie musi, działać dla Ciebie w zależności od wielkości wyjścia itp. i czy trzeba przetworzyć wyjście podprocesu podczas jego generowania.

Jak Heikki Toivonen ma sugerowane, należy spojrzeć na metodę communicate. Jednakże, powoduje to buforowanie stdout/stderr podprocesu w pamięci i otrzymujemy te zwrócone z wywołania communicate - nie jest to idealne rozwiązanie dla niektórych scenariuszy. Ale warto przyjrzeć się źródłu metody komunikacji.

Inny przykład znajduje się w pakiecie, który utrzymuję, python-gnupg, gdzie plik wykonywalny gpg jest wywoływany przez subprocess, aby wykonać ciężkie operacje, a wrapper Pythona wywołuje wątki, aby odczytać stdout i stderr gpg oraz wykorzystaj je, ponieważ dane są wytwarzane przez gpg. Być może będziesz w stanie uzyskać kilka pomysłów, patrząc na źródło tam, jak również. Dane generowane przez gpg zarówno na stdout, jak i stderr mogą być dość duże, w ogólnym przypadku.

 6
Author: Vinay Sajip,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-07-25 19:14:53

Odczyt stdout i stderr niezależnie z bardzo dużym wyjściem (tj. dużą ilością megabajtów) przy użyciu 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)
 6
Author: vz0,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-12-02 10:20:49

Możesz spróbować się porozumieć i zobaczyć, czy to rozwiąże twój problem. Jeśli nie, przekierowałbym wyjście do pliku tymczasowego.

 2
Author: Heikki Toivonen,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-07-24 23:24:10

Miałem ten sam problem. Jeśli musisz obsługiwać duże wyjście, inną dobrą opcją może być użycie pliku dla stdout i stderr i przekazanie tych plików według parametru.

Sprawdź moduł tempfile w Pythonie: https://docs.python.org/2/library/tempfile.html .

Coś takiego może zadziałać

out = tempfile.NamedTemporaryFile(delete=False)

Wtedy zrobiłbyś:

Popen(... stdout=out,...)

Następnie możesz odczytać plik i usunąć go później.

 1
Author: Mariano Anaya,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-07-24 20:28:30

Oto proste podejście, które rejestruje zarówno zwykłe wyjście, jak i wyjście błędów, wszystkie w Pythonie, więc ograniczenia w stdout nie mają zastosowania:

com_str = 'uname -a'
command = subprocess.Popen([com_str], stdout=subprocess.PIPE, shell=True)
(output, error) = command.communicate()
print output

Linux 3.11.0-20-generic SMP Fri May 2 21:32:55 UTC 2014 

I

com_str = 'id'
command = subprocess.Popen([com_str], stdout=subprocess.PIPE, shell=True)
(output, error) = command.communicate()
print output

uid=1000(myname) gid=1000(mygrp) groups=1000(cell),0(root)
 0
Author: SDsolar,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-03-29 05:41:11