Écrire une sortie standard Python dans un fichier immédiatement

51

Lorsque vous essayez d'écrire la sortie standard d'un script Python dans un fichier texte ( python script.py > log), le fichier texte est créé au démarrage de la commande, mais le contenu réel n'est pas écrit jusqu'à la fin du script Python. Par exemple:

script.py:

import time
for i in range(10):
    print('bla')
    time.sleep(5)

Imprime vers stdout toutes les 5 secondes lorsqu'il est appelé avec python script.py, mais lorsque j'appelle python script.py > log, la taille du fichier journal reste égale à zéro jusqu'à la fin du script. Est-il possible d'écrire directement dans le fichier journal, de sorte que vous puissiez suivre la progression du script (par exemple, utiliser tail)?

EDIT Il s'avère que ça python -u script.pyfait l'affaire, je ne connaissais pas la mise en tampon de stdout.

Bart
la source
1
@jezmck, j'aurais pu mal comprendre la question.
Zyxue

Réponses:

64

Cela se produit car, normalement, lorsque le processus STDOUT est redirigé vers autre chose qu'un terminal, la sortie est alors stockée dans une mémoire tampon de la taille d'un système d'exploitation (4 ou 8k dans certains cas). Inversement, lors de la sortie vers un terminal, STDOUT sera mis en mémoire de ligne ou pas du tout, de sorte que vous verrez la sortie après chaque \nou pour chaque caractère.

Vous pouvez généralement changer la mise en mémoire tampon STDOUT avec l' stdbufutilitaire:

stdbuf -oL python script.py > log

Maintenant, si vous tail -F log, vous devriez voir chaque ligne sortie immédiatement comme elle est générée.


De même, un vidage explicite du flux de sortie après chaque impression devrait permettre d'obtenir le même résultat. Il semble que cela sys.stdout.flush()devrait atteindre cela en Python. Si vous utilisez Python 3.3 ou plus récent, la printfonction a aussi un flushmot - clé qui fait cela: print('hello', flush=True).

Trauma numérique
la source
8
Merci, je ne savais pas à propos de la mise en mémoire tampon! Sachant cela, Google m'a rapidement dit que python -u script.pyc'était la solution. EDIT Tant de réponses à la fois, j’ai accepté la tienne car elle m’a orienté dans la direction de la mise en mémoire tampon.
Bart
1
@ julbra Cool, oui, je ne savais pas que python avait cette option non plus. Certains programmes en ligne de commande ont également des options similaires - par exemple, --line-bufferedpour grep, mais d'autres pas. stdbufest l'utilitaire général fourre-tout pour traiter ceux qui ne le font pas.
Digital Trauma
@ DigitalTrauma: Ne vaut-il pas mieux ne pas utiliser de tampon, c'est- stdbuf -o0 python script.py > logà- dire dans ce genre de circonstances déterminées?
Heemayl
@heemayl -oLest un compromis. En général, des mémoires tampons plus grandes offrent de meilleures performances lors de la redirection quelque part (moins d'appels système et moins d'opérations d'E / S). Cependant, s'il est absolument nécessaire de voir chaque caractère lors de la sortie, alors oui, -o0il sera requis.
Digital Trauma
@Paul Veuillez éviter de copier-coller le contenu entre les réponses, ou au moins de mentionner les auteurs originaux qui ont fourni le contenu.
Bakuriu
44

Cela devrait faire le travail:

import time, sys
for i in range(10):
    print('bla')
    sys.stdout.flush()
    time.sleep(5)

Comme Python va mettre le tampon stdoutpar défaut, j'ai utilisé ici sys.stdout.flush()pour vider le tampon.

Une autre solution consisterait à utiliser le -ucommutateur (non tamponné) de python. Donc, ce qui suit fera aussi:

python -u script.py >> log
heemayl
la source
11

Une variation sur le thème de l'utilisation de l'option propre de python pour une sortie non tamponnée consisterait à utiliser #!/usr/bin/python -ucomme première ligne.

Avec #!/usr/bin/env pythoncet argument supplémentaire, ça ne marchera pas, alors alternativement, on pourrait courir PYTHONUNBUFFERED=1 ./my_scriipt.py > output.txtou le faire en deux étapes:

$ export PYTHONUNBUFFERED=1
$ ./myscript.py
Sergiy Kolodyazhnyy
la source
10

Vous devriez passer flush=Trueà la printfonction:

import time

for i in range(10):
    print('bla', flush=True)
    time.sleep(5)

Selon la documentation, par défaut, printn'impose rien au nettoyage:

Le choix de la sortie en mémoire tampon est généralement déterminé par fichier, mais si l' flushargument mot clé est true, le flux est vidé de force.

Et la documentation pour sysles strems dit:

Lorsqu'ils sont interactifs, les flux standard sont mis en tampon de ligne. Sinon, ils sont mis en mémoire tampon comme des fichiers texte normaux. Vous pouvez remplacer cette valeur avec l' -uoption de ligne de commande.


Si vous êtes coincé avec une ancienne version de python, vous devez appeler la flushméthode du sys.stdoutflux:

import sys
import time

for i in range(10):
    print('bla')
    sys.stdout.flush()
    time.sleep(5)
Bakuriu
la source
1
L'argument flush = True fonctionne bien avec Python 3.4.2, mais ne fonctionne pas avec l'ancien (..) Python 2.7.9
Bart
Cette réponse suggère la même chose que DigitalTrauma10 heures avant. Vous devriez upvoter son post, ne pas poster la même chose à nouveau.
dotancohen
4
@dotancohen En fait, la partie concernant a print(flush=True)été ajoutée à cette réponse après la mienne par un auteur tiers. Je trouve cela de mauvais goût d'extraire le contenu de ma réponse pour les mettre dans un autre sans crédit. J'ai décidé d'ajouter ma réponse uniquement parce qu'aucune réponse ne mentionnait le moyen le plus simple de réaliser ce que le PO souhaitait dans les versions plus récentes de Python, et j'ai ajouté le "moyen ancien" juste pour être complet. La prochaine fois, veuillez vérifier l'historique des révisions avant de commenter et / ou de voter.
Bakuriu
@ Bakuriu: Je suis désolé alors! Cela montre une bonne raison de toujours poster pourquoi lors d’un vote à la baisse . Pourriez-vous s'il vous plaît éditer le message un peu afin que je puisse changer mon vote vers le bas pour un vote vers le haut? Je vous remercie!
dotancohen
Il devrait travailler avec Python 2.7 si vous faites l' __future__importation: from __future__ import print_function. Mais oui, c'est uniquement pour la compatibilité avec Python 3
Sergiy Kolodyazhnyy