Sottoprocesso persistente di Python
-
12-11-2019 - |
Domanda
C'è un modo per fare una chiamata del sottoprocessa in Python "persistente"? Sto chiamando un programma che richiede un po 'di tempo per caricare più volte. Quindi sarebbe bello se potessi semplicemente lasciare quel programma aperto e comunicare con esso senza ucciderlo.
La versione dei cartoni animati della mia sceneggiatura Python sembra così:
for text in textcollection:
myprocess = subprocess.Popen(["myexecutable"],
stdin = subprocess.PIPE, stdout = subprocess.PIPE,
stderr = None)
myoutputtext, err = myprocess.communicate(input=text)
Devo elaborare ogni testo separatamente, quindi unire tutto in un file di testo di grandi dimensioni ed elaborarlo 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, lo apprezzerei davvero.
Soluzione
Puoi usare myprocess.stdin.write()
e myprocess.stdout.read()
Per comunicare con il tuo sottoprocesso, devi solo stare attento a assicurarti di gestire il buffering correttamente per evitare che le chiamate bloccino.
Se l'output del tuo sottoprocesso è ben definito, dovresti essere in grado di comunicare in modo affidabile con esso usando il buffering 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 è mettere la maniglia del file in modalità non bloccante, che consentirà di chiamare funzioni come myprocess.stdout.read()
e falli restituire i dati se presenti sono disponibili o sollevare un IOError
Se non ci sono dati:
>>> 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
Questo ti permetterebbe di fare qualcosa del genere:
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 dovresti scrivere che ritorna True
Se i dati che hai ricevuto finora sono tutti output che ti aspetti di ottenere.
Altri suggerimenti
È la chiamata a communicate()
Questo sta uccidendo il tuo sottoprocesso. Secondo il Documentazione del sottoprocesso il communicate()
Metodo lo farà:
Interagisci con il processo: inviare dati a stdin. Leggi i dati di StDout e Stderr, fino a raggiungere la fine del file. Attendere il processo di risoluzione.
Quello che vuoi fare è interagire direttamente con il POpen
oggetto stdin
e stdout
Proprietà direttamente per comunicare con il sottoprocesso. Tuttavia, la documentazione consiglia di questo detto:
ATTENZIONE: Usa comunicare () anziché .stdin.Write, .stdout.read o .stderr.read per evitare i deadlock a causa di uno qualsiasi degli altri tubi del sistema operativo che riempiono e bloccano il processo del bambino.
Quindi devi implementare le tue soluzioni alternative per potenziali deadlock o sperare che qualcuno abbia scritto un Modulo di sottoprocesso asincrono per te.
Modificare: Ecco un rapido esempio di come si potrebbe usare il modulo di sottoprocesso asincrono:
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 eseguo questo, stampe:
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
Penso che tu stia cercando
myprocess.stdin.write(text)
È possibile creare un elenco di pipia e quindi chiamare comunicare su ciascun elemento in un altro ciclo. qualcosa come questo
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
In questo modo non dovrà aspettare fino a quando tutte le sorte sono iniziate
if os.name == 'nt':
startupinfo = subprocess.STARTUPINFO()
startupinfo.dwFlags |= subprocess._subprocess.STARTF_USESHOWWINDOW
subprocess.call(os.popen(tempFileName), shell=True)
os.remove(tempFileName)