Mon script python utilise un sous-processus pour appeler un utilitaire Linux très bruyant. Je souhaite stocker toutes les sorties dans un fichier journal et en montrer une partie à l'utilisateur. Je pensais que ce qui suit fonctionnerait, mais la sortie ne s'affiche pas dans mon application tant que l'utilitaire n'en a pas généré une quantité significative.
#fake_utility.py, just generates lots of output over time
import time
i = 0
while True:
print hex(i)*512
i += 1
time.sleep(0.5)
#filters output
import subprocess
proc = subprocess.Popen(['python','fake_utility.py'],stdout=subprocess.PIPE)
for line in proc.stdout:
#the real code does filtering here
print "test:", line.rstrip()
Le comportement que je souhaite vraiment est que le script de filtrage imprime chaque ligne telle qu'elle est reçue du sous-processus. Un peu comme ce que tee
fait mais avec du code python.
Qu'est-ce que je rate? Est-ce seulement possible?
Mettre à jour:
Si une sys.stdout.flush()
est ajoutée à fake_utility.py, le code a le comportement souhaité dans Python 3.1. J'utilise Python 2.6. Vous penseriez que l'utilisation de proc.stdout.xreadlines()
fonctionnerait de la même façon que py3k, mais ce n'est pas le cas.
Mise à jour 2:
Voici le code de travail minimal.
#fake_utility.py, just generates lots of output over time
import sys, time
for i in range(10):
print i
sys.stdout.flush()
time.sleep(0.5)
#display out put line by line
import subprocess
proc = subprocess.Popen(['python','fake_utility.py'],stdout=subprocess.PIPE)
#works in python 3.0+
#for line in proc.stdout:
for line in iter(proc.stdout.readline,''):
print line.rstrip()
Cela fait longtemps que je n'ai pas travaillé pour la dernière fois avec Python, mais je pense que le problème vient de l'instruction for line in proc.stdout
, qui lit l'intégralité de l'entrée avant de la parcourir. La solution consiste à utiliser readline()
à la place:
#filters output
import subprocess
proc = subprocess.Popen(['python','fake_utility.py'],stdout=subprocess.PIPE)
while True:
line = proc.stdout.readline()
if line != '':
#the real code does filtering here
print "test:", line.rstrip()
else:
break
Bien sûr, vous devez toujours gérer la mise en mémoire tampon du sous-processus.
Remarque: selon la documentation la solution avec un itérateur devrait être équivalente à utiliser readline()
, à l'exception du tampon de lecture anticipée, mais (ou exactement à cause de cela), la modification proposée a produit des résultats différents pour moi (Python 2.5 sous Windows XP).
Peu en retard à la fête, mais a été surpris de ne pas voir ce que je pense être la solution la plus simple ici:
import io
import subprocess
proc = subprocess.Popen(["prog", "arg"], stdout=subprocess.PIPE)
for line in io.TextIOWrapper(proc.stdout, encoding="utf-8"): # or another encoding
# do something with line
En effet, si vous avez trié l'itérateur, la mise en mémoire tampon pourrait maintenant être votre problème. Vous pourriez dire au python du sous-processus de ne pas mettre sa sortie en mémoire tampon.
proc = subprocess.Popen(['python','fake_utility.py'],stdout=subprocess.PIPE)
devient
proc = subprocess.Popen(['python','-u', 'fake_utility.py'],stdout=subprocess.PIPE)
J'en ai eu besoin en appelant python à partir de python.
Vous voulez passer ces paramètres supplémentaires à subprocess.Popen
:
bufsize=1, universal_newlines=True
Ensuite, vous pouvez itérer comme dans votre exemple. (Testé avec Python 3.5)
La modification suivante de la réponse de Rômulo fonctionne pour moi sur Python 2 et 3 (2.7.12 et 3.6.1):
import os
import subprocess
process = subprocess.Popen(command, stdout=subprocess.PIPE)
while True:
line = process.stdout.readline()
if line != '':
os.write(1, line)
else:
break
Fonction permettant d'itérer simultanément stdout
et stderr
simultanément, en temps réel, ligne par ligne
Si vous devez obtenir le flux de sortie pour stdout
et stderr
simultanément, vous pouvez utiliser la fonction suivante.
La fonction utilise des files d'attente pour fusionner les deux canaux de Popen en un seul itérateur.
Nous créons ici la fonction read_popen_pipes()
:
from queue import Queue, Empty
from concurrent.futures import ThreadPoolExecutor
def enqueue_output(file, queue):
for line in iter(file.readline, ''):
queue.put(line)
file.close()
def read_popen_pipes(p):
with ThreadPoolExecutor(2) as pool:
q_stdout, q_stderr = Queue(), Queue()
pool.submit(enqueue_output, p.stdout, q_stdout)
pool.submit(enqueue_output, p.stderr, q_stderr)
while True:
if p.poll() is not None and q_stdout.empty() and q_stderr.empty():
break
out_line = err_line = ''
try:
out_line = q_stdout.get_nowait()
err_line = q_stderr.get_nowait()
except Empty:
pass
yield (out_line, err_line)
read_popen_pipes()
en cours d'utilisation:
import subprocess as sp
with sp.Popen(my_cmd, stdout=sp.PIPE, stderr=sp.PIPE, text=True) as p:
for out_line, err_line in read_popen_pipes(p):
# Do stuff with each line, e.g.:
print(out_line, end='')
print(err_line, end='')
return p.poll() # return status-code
Vous pouvez également lire les lignes sans boucle. Fonctionne en python3.6.
import os
import subprocess
process = subprocess.Popen(command, stdout=subprocess.PIPE)
list_of_byte_strings = process.stdout.readlines()
J'ai essayé cela avec python3 et cela a fonctionné, source
def output_reader(proc):
for line in iter(proc.stdout.readline, b''):
print('got line: {0}'.format(line.decode('utf-8')), end='')
def main():
proc = subprocess.Popen(['python', 'fake_utility.py'],
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT)
t = threading.Thread(target=output_reader, args=(proc,))
t.start()
try:
time.sleep(0.2)
import time
i = 0
while True:
print (hex(i)*512)
i += 1
time.sleep(0.5)
finally:
proc.terminate()
try:
proc.wait(timeout=0.2)
print('== subprocess exited with rc =', proc.returncode)
except subprocess.TimeoutExpired:
print('subprocess did not terminate in time')
t.join()