2012-01-23 12 views
10

¿Hay alguna manera de hacer que una llamada de subproceso en python sea "persistente"? Estoy llamando a un programa que tarda un tiempo en cargar varias veces. Entonces sería genial si pudiera dejar ese programa abierto y comunicarme con él sin matarlo.Subproceso de python persistente

La versión de dibujos animados de mi script en Python es así:

for text in textcollection: 
    myprocess = subprocess.Popen(["myexecutable"], 
       stdin = subprocess.PIPE, stdout = subprocess.PIPE, 
       stderr = None) 
    myoutputtext, err = myprocess.communicate(input=text) 

necesito para procesar cada texto por separado, para unirlo todo en un archivo de texto grande y el procesamiento de una vez no es una opción.

Preferiblemente, si hay una opción como esta

myprocess = subprocess.Popen(["myexecutable"], 
      stdin = subprocess.PIPE, stdout = subprocess.PIPE, 
      stderr = None) for text in textcollection: 
for text in textcollection: 
    myoutputtext, err = myprocess.communicate(input=text) 

donde pueda salir del proceso abierto, lo agradecería mucho.

Respuesta

24

Puede utilice myprocess.stdin.write() y myprocess.stdout.read() para comunicarse con su subproceso, solo debe tener cuidado para asegurarse de manejar el almacenamiento en búfer correctamente para evitar que sus llamadas se bloqueen.

Si la salida de su subproceso está bien definida, debe poder comunicarse con ella de forma fiable mediante el almacenamiento en línea y myprocess.stdout.readline().

Aquí se muestra un ejemplo:

>>> p = subprocess.Popen(['cat'], bufsize=1, stdin=subprocess.PIPE, stdout=subprocess.PIPE) 
>>> p.stdin.write('hello world\n') 
>>> p.stdout.readline() 
'hello world\n' 
>>> p.stdout.readline()  # THIS CALL WILL BLOCK 

Una alternativa a este método para Unix es poner el identificador de archivo en no-bloqueo, lo que le permitirá llamar a funciones como myprocess.stdout.read() y tienen que volver datos si ninguna está disponible, o lanzar una IOError si no hay ningún dato:

>>> p = subprocess.Popen(['cat'], stdin=subprocess.PIPE, stdout=subprocess.PIPE) 
>>> import fcntl, os 
>>> fcntl.fcntl(p.stdout.fileno(), fcntl.F_SETFL, os.O_NONBLOCK) 
0 
>>> p.stdout.read()   # raises an exception instead of blocking 
Traceback (most recent call last): 
    File "<stdin>", line 1, in <module> 
IOError: [Errno 11] Resource temporarily unavailable 

esto le permitiría a hacer algo como esto:

fcntl.fcntl(p.stdout.fileno(), fcntl.F_SETFL, os.O_NONBLOCK) 
for text in textcollection: 
    myprocess.stdin.write(text + '\n') 
    while True: 
     myoutputtext = '' 
     try: 
      myoutputtext += myprocess.stdout.read() 
     except IOError: 
      pass 
     if validate_output(myoutputtext): 
      break 
     time.sleep(.1) # short sleep before attempting another read 

En este ejemplo, validate_output() es una función que necesitaría escribir que devuelve True si los datos que ha recibido hasta ahora son todos de salida que espera obtener.

+1

¡Gracias! Me gusta su solución lo mejor ya que no requiere una descarga de terceros. Lamentablemente, no funciona para mí. Después de probar algunas cosas, estoy bastante seguro de que es un problema con el programa Java que estoy llamando en lugar de su solución, por lo que su solución es buena. – JasonMond

+0

¿Por qué votar abajo? –

+0

Esto fue por error. Mi upvote está inactivo hasta que se edite cualquier cosa, pero no veo nada para mejorar o no. Respuesta perfecta. – hynekcer

1

creo que estás buscando

myprocess.stdin.write(text) 

se puede crear una lista de POPENS y luego llamar a comunicar en cada elemento en otro bucle. algo como esto

processes=[] 
for text in textcollection: 
    myprocess = subprocess.Popen(["myexecutable"], 
       stdin = subprocess.PIPE, stdout = subprocess.PIPE, 
       stderr = None) 
    myprocess.stdin.write(text) 
    processes.append(myprocess) 

for proc in processes: 
    myoutput, err=proc.communicate() 
    #do something with the output here 

esta manera no tendrá que esperar hasta después de que todos los POPENS han comenzado

+0

Lamentablemente, esto no funcionará para mí porque es un programa de Java que se come alrededor de 3G de memoria en cada ejecución. Por eso lleva tanto tiempo cargar. No puedo tener 5000 instancias de un proceso 3G. – JasonMond

+0

Creo que entiendo. Después de obtener el texto de entrada, ¿sale algo y luego sale? o espera a que ingrese algo más –

+0

Salidas y luego sale. – JasonMond

5

Es la llamada a communicate() que está matando a su subproceso. De acuerdo con la subprocess documentation el método communicate() será:

Interactuar con proceso: Enviar datos a la entrada estándar. Lea los datos de stdout y stderr, hasta llegar al final del archivo. Espere a que el proceso finalice.

Lo que se quiere hacer es interactuar directamente con los POpenstdin y stdout propiedades del objeto directamente para comunicarse con el subproceso. Sin embargo, la documentación desaconseja este dicho:

Advertencia: el uso comunicarse() en lugar de .stdin.write, .stdout.read o .stderr.read para evitar los puntos muertos debido a cualquiera de los otros tampones de tubería de llenado OS y bloquear el proceso del niño.

Por lo tanto, debe implementar sus propias soluciones para posibles interbloqueos, o esperar que alguien le haya escrito un asynchronous subprocess module.

Editar: He aquí un ejemplo de cómo quick'n'dirty se podría utilizar el módulo subproceso asíncrono:

import asyncsubprocess 

textcollection = ['to', 'be', 'or', 'not', 'to be', 'that is the', 'question'] 

myprocess = asyncsubprocess.Popen(["cat"], 
    stdin = asyncsubprocess.PIPE, 
    stdout = asyncsubprocess.PIPE, 
    stderr = None) 

for text in textcollection: 
    bytes_sent, myoutput, err = myprocess.listen(text) 
    print text, bytes_sent, myoutput, err 

Cuando ejecuto esto, se imprime:

to 2 to 
be 2 be 
or 2 or 
not 3 not 
to be 5 to be 
that is the 11 that is the 
question 8 question 
-2
if os.name == 'nt': 
startupinfo = subprocess.STARTUPINFO() 
startupinfo.dwFlags |= subprocess._subprocess.STARTF_USESHOWWINDOW 
subprocess.call(os.popen(tempFileName), shell=True) 
os.remove(tempFileName)