Schreiben Sie Python stdout sofort in die Datei

51

Beim Versuch, die Standardausgabe von einem Python-Skript in eine Textdatei ( python script.py > log) zu schreiben , wird die Textdatei beim Starten des Befehls erstellt, der eigentliche Inhalt wird jedoch erst nach Abschluss des Python-Skripts geschrieben. Zum Beispiel:

script.py:

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

druckt alle 5 Sekunden auf stdout, wenn mit aufgerufen wird python script.py, aber wenn ich aufrufe python script.py > log, bleibt die Größe der Protokolldatei Null, bis das Skript beendet ist. Ist es möglich, direkt in die Protokolldatei zu schreiben, sodass Sie den Fortschritt des Skripts verfolgen können (z. B. mithilfe von tail)?

EDIT Es stellt sich heraus, dass python -u script.pydies der Trick ist, ich wusste nichts über das Puffern von stdout.

Bart
quelle
1
@jezmck, ich hätte die Frage falsch verstehen können.
Zyxue

Antworten:

64

Dies geschieht normalerweise, weil, wenn der Prozess STDOUT zu etwas anderem als einem Terminal umgeleitet wird, die Ausgabe in einem betriebssystemspezifischen Puffer gepuffert wird (in vielen Fällen vielleicht 4k oder 8k). Umgekehrt wird STDOUT bei der Ausgabe an ein Terminal zeilenweise oder gar nicht gepuffert, sodass die Ausgabe nach jedem \noder für jedes Zeichen erfolgt.

Sie können die STDOUT-Pufferung im Allgemeinen mit dem stdbufDienstprogramm ändern :

stdbuf -oL python script.py > log

Wenn Sie dies jetzt tun tail -F log, sollten Sie jede Zeilenausgabe sofort sehen, wenn sie generiert wird.


Alternativ sollte das explizite Spülen des Ausgabestreams nach jedem Druck dasselbe erreichen. Es sieht so aus, als sys.stdout.flush()sollte dies in Python erreicht werden. Wenn Sie Python 3.3 oder höher verwenden, die printhat die Funktion auch ein flushSchlüsselwort , das dies tut: print('hello', flush=True).

Digitales Trauma
quelle
8
Danke, ich wusste nichts über das Puffern! Da Google das wusste, sagte es mir ziemlich schnell, dass python -u script.pydies der Trick ist. BEARBEITEN So viele Antworten auf einmal, ich habe Ihre angenommen, da sie mich in Richtung Pufferung wies.
Bart
1
@ Julbra Cool, ja, ich wusste nicht, dass Python diese Option hatte. Einige Befehlszeilenprogramme haben ähnliche Optionen - z. B. --line-bufferedfür grep, andere jedoch nicht. stdbufist das allgemeine Hilfsprogramm, um mit denen umzugehen, die es nicht tun.
Digital Trauma
@DigitalTrauma: Ist es nicht besser, überhaupt keine Pufferung zu verwenden, dh stdbuf -o0 python script.py > logunter diesen bestimmten Umständen?
Heemayl
@heemayl -oList ein Kompromiss. Im Allgemeinen bieten größere Puffer eine bessere Leistung bei der Umleitung (weniger Systemaufrufe und weniger E / A-Vorgänge). Wenn es jedoch unbedingt erforderlich ist, jedes Zeichen so zu sehen, wie es ausgegeben wird, ist yes -o0erforderlich.
Digital Trauma
@Paul Bitte vermeiden Sie das Einfügen von Inhalten zwischen den Antworten oder erwähnen Sie zumindest die Autoren, die den Inhalt zur Verfügung gestellt haben.
Bakuriu
44

Dies sollte den Job machen:

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

Da Python das stdoutstandardmäßig puffert, habe ich hier verwendet sys.stdout.flush(), um den Puffer zu leeren.

Eine andere Lösung wäre die Verwendung des -u(ungepufferten) Schalters von python. So wird das folgende auch tun:

python -u script.py >> log
heemayl
quelle
11

Eine Variation des Themas der Verwendung von Pythons eigener Option für die ungepufferte Ausgabe wäre die Verwendung #!/usr/bin/python -uals erste Zeile.

Wenn #!/usr/bin/env pythondieses zusätzliche Argument nicht funktioniert, kann man es alternativ auch PYTHONUNBUFFERED=1 ./my_scriipt.py > output.txtin zwei Schritten ausführen :

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

Sie sollten flush=Truean die printFunktion übergeben:

import time

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

Gemäß der Dokumentation wird standardmäßig printnichts zum Löschen erzwungen:

Ob die Ausgabe gepuffert wird, hängt normalerweise von der Datei ab. Wenn jedoch das flushSchlüsselwortargument wahr ist, wird der Stream zwangsweise gelöscht.

Und die Dokumentation für sys's Strems sagt:

Wenn interaktiv, werden Standard-Streams zeilenweise gepuffert. Ansonsten werden sie wie normale Textdateien blockgepuffert. Sie können diesen Wert mit der -uBefehlszeilenoption überschreiben.


Wenn Sie mit einer alten Version von Python nicht weiterkommen, müssen Sie die flushMethode des sys.stdoutStreams aufrufen :

import sys
import time

for i in range(10):
    print('bla')
    sys.stdout.flush()
    time.sleep(5)
Bakuriu
quelle
1
Das Argument flush = True funktioniert gut mit Python 3.4.2, in der Tat nicht mit dem alten (..) Python 2.7.9
Bart
Diese Antwort deutet auf dasselbe hin, was DigitalTrauma10 Stunden zuvor gesagt wurde. Du solltest seinen Beitrag positiv bewerten, nicht das Gleiche noch einmal posten.
Dotancohen
4
@dotancohen Eigentlich wurde der Teil über print(flush=True)diese Antwort nach meiner von einem Drittautor hinzugefügt . Ich finde es geschmacklos, Inhalte aus meiner Antwort herauszureißen, um sie ohne Anerkennung in eine andere zu stecken. Ich beschloss, meine Antwort nur deshalb hinzuzufügen, weil in keiner Antwort der einfachste Weg genannt wurde, das zu erreichen, was das OP in neueren Versionen von Python wollte, und ich fügte der Vollständigkeit halber den "alten Weg" hinzu. Bitte überprüfen Sie das nächste Mal den Revisionsverlauf, bevor Sie Kommentare abgeben oder Abstimmungen vornehmen.
Bakuriu
@ Bakuriu: Es tut mir leid dann! Dies ist ein guter Grund, beim Downvoting immer das Warum zu posten . Könnten Sie bitte den Beitrag ein wenig bearbeiten, damit ich meine Gegenstimme in eine Gegenstimme ändern kann? Danke!
Dotancohen
Es sollte mit Python 2.7 arbeiten , wenn Sie tun __future__Import: from __future__ import print_function. Aber ja, das dient nur der Kompatibilität mit Python 3
Sergiy Kolodyazhnyy