2009-07-24 18 views
26

Tengo un código Python que ejecuta una aplicación externa que funciona bien cuando la aplicación tiene una pequeña cantidad de salida, pero se cuelga cuando hay mucho. Mi código se parece a:Usando subproceso.Popen para proceso con salida grande

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

Hay comentarios en los documentos que parecen indicar el posible problema. Bajo espera, hay:

Advertencia: Este será un punto muerto si el proceso hijo genera suficiente salida a un tubo de stdout o stderr tal que bloquea la espera de la memoria intermedia de la tubería del sistema operativo para aceptar más datos. Use communicate() para evitar eso.

aunque bajo comunicarse, veo:

Nota leer los datos se almacena temporalmente en la memoria, por lo que no utilizan este método si el tamaño de los datos es grande o ilimitado.

Así que no está claro para mí que debería usar cualquiera de estos si tengo una gran cantidad de datos. No indican qué método debo usar en ese caso.

Necesito el valor de retorno del ejecutivo y realizo el análisis y uso el stdout y el stderr.

Entonces, ¿cuál es un método equivalente en Python para ejecutar una aplicación externa que va a tener un gran rendimiento?

+3

Parece "grande" en el comunican documentación es * mucho más grande * que es probable que esté esperando, y ciertamente mucho más grande que lo común. Por ejemplo, puede generar 10 MB de texto y la mayoría de los sistemas estarían bien para comunicarse. La salida de 1GB cuando solo tienes 1GB de RAM sería otra historia. –

Respuesta

16

Estás haciendo el bloqueo lee a dos archivos; el primero debe completarse antes de que comience el segundo. Si la aplicación escribe mucho en stderr, y nada en stdout, el proceso se quedará esperando datos en stdout que no vengan, mientras el programa que está ejecutando se quede allí esperando que se lea el material que escribió al stderr (que nunca será, ya que está esperando stdout).

Hay varias formas de solucionarlo.

Lo más simple es no interceptar stderr; dejar stderr=None. Los errores se enviarán a stderr directamente. No puede interceptarlos y mostrarlos como parte de su propio mensaje. Para las herramientas de línea de comandos, esto a menudo está bien. Para otras aplicaciones, puede ser un problema.

Otro enfoque simple es redirigir stderr a stdout, por lo que solo tiene un archivo entrante: establecer stderr=STDOUT. Esto significa que no puede distinguir la salida regular de la salida de error. Esto puede o no ser aceptable, dependiendo de cómo la aplicación escribe la salida.

La forma completa y complicada de manejar esto es select (http://docs.python.org/library/select.html). Esto le permite leer de forma no bloqueante: obtiene datos siempre que los datos aparecen en stdout o stderr. Solo recomendaría esto si es realmente necesario.Esto probablemente no funciona en Windows.

+0

Dado que el caso específico que estoy tratando tendrá una gran cantidad de stdout y una pequeña cantidad o ninguna stderr, voy a probar la redirección de archivos que Mark sugirió primero, pero la cobertura más completa del problema es muy útil. – Tim

6

Una gran cantidad de la producción es subjetiva por lo que es un poco difícil de hacer una recomendación. Si la cantidad de salida es realmente grande, entonces probablemente no quiera capturarlo todo con una sola llamada a read(). Es posible que desee intentar escribir la salida a un archivo y luego extraer los datos en forma incremental, como por ejemplo:

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

Esto también puede bloquear fácilmente. Si el proceso bifurcado escribe más datos de los que el sistema operativo almacenará en búfer a stderr antes de salir con un código de error, este código permanecerá esperando su salida para siempre, mientras el proceso se encuentra en una escritura de bloqueo en stderr esperando a que lo lea. –

+0

1) asume que la salida de datos grandes es stderr, lo cual sería extraño pero no desconocido), 2) si stderr ES la fuente de cantidad de datos grandes, la solución es la misma, haga que stderr también sea un archivo –

+0

En este caso, el proceso puede potencialmente tiene una gran cantidad de stdout, pero no tendrá mucho, si alguno, stderr, por lo que esta es una solución razonable para mí. – Tim

2

Puede intentar comunicarse y ver si eso resuelve su problema. Si no, redireccionaría la salida a un archivo temporal.

+0

Dado que la comunicación explícitamente advierte sobre el uso si tiene una gran cantidad de resultados, voy a ver las otras opciones. – Tim

6

Glenn Maynard tiene razón en su comentario sobre interbloqueos. Sin embargo, la mejor manera de resolver este problema es crear dos subprocesos, uno para stdout y otro para stderr, que leen esos flujos respectivos hasta que se agoten y hagan lo que necesiten con la salida.

La sugerencia de usar archivos temporales puede o no funcionar para usted, dependiendo del tamaño de la salida, etc. y de si necesita procesar la salida del subproceso a medida que se genera.

Como Heikki Toivonen ha sugerido, debe consultar el método communicate. Sin embargo, esto almacena el stdout/stderr del subproceso en la memoria y usted obtiene los devueltos de la llamada communicate; esto no es ideal para algunos escenarios. Pero la fuente del método de comunicación vale la pena mirar.

Otro ejemplo está en un paquete mantengo, python-gnupg, donde el gpg ejecutable se genera a través de subprocess a hacer el trabajo pesado, y la envoltura Python genera subprocesos para leer stdout y stderr de gpg y los consuma como datos es producida por gpg . También puede obtener algunas ideas mirando la fuente allí. Los datos producidos por gpg para stdout y stderr pueden ser bastante grandes, en el caso general.

+0

Verificaremos python-gnupg como ejemplo. Gracias. – Tim

+1

Enlaces relevantes a los métodos interesantes - ['_open_subprocess'] (https://bitbucket.org/vinay.sajip/python-gnupg/src/952281d4c966608403a23af76429f11df9e0a852/gnupg.py?at=default&fileviewer=file-view-default#gnupg. py-825) y ['_collect_output'] (https://bitbucket.org/vinay.sajip/python-gnupg/src/952281d4c966608403a23af76429f11df9e0a852/gnupg.py?at=default&fileviewer=file-view-default#gnupg.py-903) – neowulf33

1

Tuve el mismo problema. Si tiene que manejar una salida grande, otra buena opción podría ser usar un archivo para stdout y stderr, y pasar esos archivos por parámetro.

Compruebe el módulo de tempfile en python: https://docs.python.org/2/library/tempfile.html.

Algo como esto podría funcionar

out = tempfile.NamedTemporaryFile(delete=False) 

allí tendría que hacer:

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

continuación, puede leer el archivo y borrarlo después.

5

lectura stdout y stderr forma independiente con salida muy grande (es decir, una gran cantidad de megabytes) utilizando 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

Esto, por mucho, tiene más sentido para mí en la superación de los problemas de memoria en el búfer. Incluso probé el subproceso 'cmd' como' bash -c 'cat/dev/urandom | tr -dc' a-zA-Z0-9 '"' que funciona muy bien. Mi bloqueo mental estaba alrededor de lo que significan estas líneas: [1] 'listo [0]' y ¿por qué [2] 'len (proc.stdout.read (1024)) == 0' significa EOF? [3] ¿Por qué no verificar el 'len (proc.stderr.read (1024))'? [4] ¿Por qué no es necesario leer el enjuague? Lo sentimos, varias preguntas todas agrupadas en un comentario:/ – neowulf33

+1

@ neowulf33 [1] lista es una lista de listas, listo [0] es la lista que puede contener stdout, stderr o ambas. ver seleccionar documentos. [2] "Se devuelve una cadena vacía cuando se encuentra EOF inmediatamente". https://docs.python.org/2.7/library/stdtypes.html#file.read [3] porque perderías los datos. [4] No entiendo, ¿cómo de color? – vz0

+0

Gracias! Malo - ¡Estaba medio dormido cuando escribí "leer el color"! – neowulf33

Cuestiones relacionadas