2012-01-23 3 views
10

C'è un modo per effettuare una chiamata di sottoprocesso in python "persistente"? Sto chiamando un programma che richiede un po 'di tempo per caricare più volte. Quindi sarebbe bello se potessi lasciare quel programma aperto e comunicare con esso senza ucciderlo.Sottoprocesso Python persistente

La versione a fumetti del mio script python assomiglia a questo:

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

Ho bisogno di elaborare ogni testo separatamente, in modo che unisce il tutto in un unico grande file di testo e l'elaborazione di una volta non è un'opzione.

Preferibilmente, se c'è un'opzione come questa

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) 

dove posso lasciare il processo aperto, mi piacerebbe davvero apprezzare.

risposta

24

Puoi utilizzare myprocess.stdin.write() e myprocess.stdout.read() per comunicare con il sottoprocesso, è sufficiente fare attenzione per assicurarsi di gestire correttamente il buffering per impedire il blocco delle chiamate.

Se l'output del sottoprocesso è ben definito, è necessario essere in grado di comunicare in modo affidabile con il buffer di linea e myprocess.stdout.readline().

Ecco un esempio:

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

Un'alternativa a questo metodo per Unix è quello di mettere l'handle di file in non-blocking mode, che vi permetterà di chiamare funzioni come myprocess.stdout.read() e lo hanno restituire dati se qualunque è disponibile, o di sollevare un IOError se non c'è alcun 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 

Ciò consentirebbe di fare qualcosa di simile:

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 

In questo esempio, validate_output() è una funzione che è necessario scrivere che restituisce True se i dati ricevuti finora sono tutti gli output che si prevede di ottenere.

+1

Grazie! Mi piace la tua soluzione al meglio dal momento che non richiede il download di terze parti. Sfortunatamente, non funziona per me. Dopo aver provato alcune cose, sono abbastanza sicuro che sia un problema con il programma java che sto chiamando piuttosto che la tua soluzione, quindi la tua soluzione è buona. – JasonMond

+0

Perché il voto negativo? –

+0

Questo è stato per errore. Il mio upvote è inattivo fino a quando tutto sarà modificato, ma non vedo nulla da migliorare o non ferire. Risposta perfetta – hynekcer

1

Penso che tu sia alla ricerca di

myprocess.stdin.write(text) 

è possibile creare una lista di Popens e quindi chiamare comunicare su ogni elemento in un altro ciclo. qualcosa di simile

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 

questo modo non dovrà aspettare fino a dopo tutti i Popens hanno iniziato

+0

Sfortunatamente, questo non funzionerà per me perché è un programma Java che consuma circa 3G di memoria per ogni corsa. Questo è il motivo per cui ci vuole così tanto tempo per caricare. Non posso avere 5000 istanze di un processo 3G. – JasonMond

+0

Penso di aver capito. Dopo aver ricevuto il testo di input, emette qualcosa e quindi esce? o ti aspetta per inserire qualcos'altro –

+0

Emette quindi esce. – JasonMond

5

E 'la chiamata a communicate() che sta uccidendo il vostro sottoprocesso. Secondo il subprocess documentation il metodo communicate() sarà:

Interagisci con le procedure: Inviare dati a stdin. Leggi i dati da stdout e stderr, fino al raggiungimento della fine del file. Attendere il termine del processo.

Che cosa si vuole fare è interagire direttamente con il POpen dell'oggetto stdin e stdout proprietà direttamente per comunicare con il sottoprocesso. Tuttavia, la documentazione sconsiglia questo detto:

Attenzione: Uso comunicare() anziché .stdin.write, .stdout.read o .stderr.read evitare deadlock dovuti ad uno qualsiasi degli altri buffer OS tubazione di riempimento su e bloccando il processo figlio.

Quindi è necessario implementare i propri workaround per potenziali deadlock, o sperare che qualcuno abbia scritto un asynchronous subprocess module per voi.

Edit: Ecco un esempio di come quick'n'dirty potrebbe essere utilizzato il modulo sottoprocesso asincrona:

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 

Quando ho eseguito questo, esso stampa:

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)