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
lubstderr
, tak że blokuje oczekiwanie na akceptację większej ilości danych przez bufor potoku systemu operacyjnego. Użyjcommunicate()
, 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?
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.
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
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.
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)
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.
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.
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)
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