2009-07-24 1 views
26

Ho un codice Python che esegue un'app esterna che funziona bene quando l'app ha una piccola quantità di output, ma si blocca quando ce n'è molta. Il mio codice è simile:Utilizzo del sottoprocesso.Popen per elaborazione con uscita di grandi dimensioni

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

ci sono commenti per i documenti che sembrano indicare il potenziale problema. Sotto di attesa, c'è:

Attenzione: Questo stallo se il processo figlio genera abbastanza uscita ad un tubo stdout o stderr tale che blocca in attesa per il buffer tubo di sistema operativo per accettare altri dati. Utilizzare communicate() per evitare ciò.

però sotto comunicare, vedo:

Note I dati letti vengono accodati in memoria, quindi non utilizzare questo metodo se la dimensione dei dati è grande o illimitata.

Quindi non è chiaro per me che dovrei usare uno di questi se ho una grande quantità di dati. Non indicano quale metodo dovrei usare in quel caso.

Ho bisogno del valore restituito da exec e di analizzare e utilizzare sia lo stdout e stderr.

Quindi, qual è un metodo equivalente in Python per eseguire un'app esterna che avrà un output di grandi dimensioni?

+3

Sembra "grande" nel comunicare documentazione è * molto più grande * di quanto è probabile che aspettate, e certamente molto più grande del comune. Ad esempio, è possibile produrre 10 MB di testo e la maggior parte dei sistemi sarebbe in grado di comunicare. L'output di 1 GB quando hai solo 1 GB di RAM sarebbe un'altra storia. –

risposta

16

Si sta eseguendo il blocco delle letture su due file; il primo deve essere completato prima che inizi il secondo. Se l'applicazione scrive molto su stderr e nulla su stdout, il processo si attiverà in attesa di dati su stdout che non è in arrivo, mentre il programma in esecuzione si trova lì in attesa delle informazioni scritte su stderr da leggere (che non sarà mai, visto che stai aspettando il stdout).

Ci sono alcuni modi per risolvere questo problema.

Il più semplice è quello di non intercettare stderr; lasciare stderr=None. Gli errori verranno inviati direttamente a stderr. Non puoi intercettarli e visualizzarli come parte del tuo messaggio. Per gli strumenti a riga di comando, questo è spesso OK. Per altre app, può essere un problema.

Un altro approccio semplice consiste nel reindirizzamento di stderr in stdout, in modo da avere un solo file in entrata: impostare stderr=STDOUT. Ciò significa che non è possibile distinguere l'output regolare dall'output degli errori. Questo può o non può essere accettabile, a seconda di come l'applicazione scrive l'output.

Il modo completo e complicato di gestire questo è select (http://docs.python.org/library/select.html). Ciò ti consente di leggere in modo non bloccante: ottieni dati ogni volta che i dati vengono visualizzati su stdout o stderr. Lo consiglierei solo se è davvero necessario.Questo probabilmente non funziona in Windows.

+0

Poiché il caso specifico con cui ho a che fare avrà molto stdout e una piccola quantità o nessun stderr, ho intenzione di provare la reindirizzamento del file suggerito da Mark prima, ma la copertura più completa del problema è molto utile. – Tim

6

Un sacco di output è soggettivo, quindi è un po 'difficile fare una raccomandazione. Se la quantità di output è in realtà pari a, allora probabilmente non si vorrà accontentarlo con una sola chiamata read(). Si consiglia di provare a scrivere l'output in un file e quindi estrarre i dati in modo incrementale come:

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 
+2

Questo può anche facilmente deadlock. Se il processo biforcuto scrive più dati di quelli che il sistema operativo memorizzerà su stderr prima di uscire con un codice di errore, questo codice rimarrà per sempre in attesa di uscita, mentre il processo si trova su una scrittura di blocco su stderr in attesa di essere letto. –

+0

1) si presuppone che l'output di grandi dati sia stderr che sarebbe dispari ma non inaudito), 2) se stderr è la fonte di grandi quantità di dati la soluzione è la stessa, rendere stderr un file pure –

+0

In questo caso, il processo può potenzialmente hanno una grande quantità di stdout, ma non avrà molto, se non altro, stderr, quindi questa è una soluzione ragionevole per me. – Tim

2

Si potrebbe provare a comunicare e vedere se questo risolve il problema. In caso contrario, reindirizzare l'output in un file temporaneo.

+0

Poiché la comunicazione avvisa in modo esplicito dall'uso se si dispone di una grande quantità di output, esaminerò le altre opzioni. – Tim

6

Glenn Maynard ha ragione nel suo commento sui deadlock. Tuttavia, il modo migliore per risolvere questo problema è creare due thread, uno per stdout e uno per stderr, che leggono i rispettivi stream fino a esaurimento e fanno tutto ciò che è necessario con l'output.

Il suggerimento di utilizzare file temporanei può o non può funzionare per voi in base alla dimensione dell'output ecc. E se è necessario elaborare l'output del sottoprocesso così come viene generato.

Come suggerito da Heikki Toivonen, è necessario esaminare il metodo communicate. Tuttavia, questo buffer lo stdout/stderr del sottoprocesso in memoria e si ottiene quelli restituiti dalla chiamata communicate - questo non è l'ideale per alcuni scenari. Ma la fonte del metodo di comunicazione vale la pena guardare.

Un altro esempio è in un pacchetto sostengo, python-gnupg, dove l'eseguibile gpg viene generato tramite subprocess fare il lavoro pesante, e l'involucro Python spawn fili leggere stdout e stderr di gpg e li consumi come dati è prodotto da gpg . Potresti essere in grado di ottenere alcune idee guardando anche la fonte lì. I dati prodotti da gpg su stdout e stderr possono essere abbastanza grandi, nel caso generale.

+0

Verificherà python-gnupg come esempio. Grazie. – Tim

+1

Link pertinenti ai metodi interessanti - ['_open_subprocess'] (https://bitbucket.org/vinay.sajip/python-gnupg/src/952281d4c966608403a23af76429f11df9e0a852/gnupg.py?at=default&fileviewer=file-view-default#gnupg. py-825) e ['_collect_output'] (https://bitbucket.org/vinay.sajip/python-gnupg/src/952281d4c966608403a23af76429f11df9e0a852/gnupg.py?at=default&fileviewer=file-view-default#gnupg.py-903) – neowulf33

1

Ho avuto lo stesso problema. Se devi gestire un output di grandi dimensioni, un'altra buona opzione potrebbe essere quella di usare un file per stdout e stderr e passare quei file per parametro.

Controllare il modulo tempfile in python: https://docs.python.org/2/library/tempfile.html.

Qualcosa di simile potrebbe funzionare

out = tempfile.NamedTemporaryFile(delete=False) 

allora si dovrebbe fare:

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

Quindi è possibile leggere il file, e cancellare in un secondo momento.

5

lettura stdout e stderr modo indipendente con molto grande uscita (vale a dire, un sacco di megabyte) utilizzando 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) 
+0

Questo ha di gran lunga più significato per me nel superare i problemi del buffer di memoria. Ho anche provato il sottoprocesso 'cmd' come' bash -c "cat/dev/urandom | tr -dc 'a-zA-Z0-9'" 'che funziona alla grande. Il mio blocco mentale riguardava il significato di queste righe: [1] 'ready [0]' e perché [2] 'len (proc.stdout.read (1024)) == 0' significa EOF? [3] Perché non controllare 'len (proc.stderr.read (1024))'? [4] Perché il flush non è necessario? Ci scusiamo, diverse domande tutte raggruppate in un commento:/ – neowulf33

+1

@ neowulf33 [1] pronto è un elenco di liste, pronto [0] è l'elenco che può contenere stdout, stderr o entrambi. vedi selezionare documenti. [2] "Una stringa vuota viene restituita quando viene rilevato EOF immediatamente." https://docs.python.org/2.7/library/stdtypes.html#file.read [3] perché perdi i dati! [4] Non capisco, che colore? – vz0

+0

Grazie! Il mio male - ero assolutamente mezzo addormentato quando ho scritto "read flush"! – neowulf33