Deaktivieren Sie die Ausgabepufferung

532

Ist die Ausgabepufferung im Python-Interpreter für standardmäßig aktiviert sys.stdout?

Wenn die Antwort positiv ist, wie können Sie sie deaktivieren?

Bisherige Vorschläge:

  1. Verwenden Sie den -uBefehlszeilenschalter
  2. Wickeln Sie sys.stdoutein Objekt ein, das nach jedem Schreibvorgang gelöscht wird
  3. Setze PYTHONUNBUFFEREDenv var
  4. sys.stdout = os.fdopen(sys.stdout.fileno(), 'w', 0)

Gibt es eine andere Möglichkeit, während der Ausführung ein globales Flag in sys/ sys.stdoutprogrammgesteuert zu setzen?

Eli Bendersky
quelle
7
Informationen zum Drucken in Python 3 finden Sie in dieser Antwort .
Antti Haapala
1
Ich denke, ein Nachteil -uist, dass es nicht für kompilierten Bytecode oder für Apps mit einer __main__.pyDatei als Einstiegspunkt funktioniert .
Akhan
Die vollständige CPython-Initialisierungslogik finden Sie hier: github.com/python/cpython/blob/v3.8.2/Python/…
Beni Cherniavsky-Paskin

Antworten:

443

Von Magnus Lycka Antwort auf eine Mailingliste :

Sie können die Pufferung für einen gesamten Python-Prozess mit "python -u" (oder #! / Usr / bin / env python -u usw.) oder durch Setzen der Umgebungsvariablen PYTHONUNBUFFERED überspringen.

Sie können sys.stdout auch durch einen anderen Stream wie den Wrapper ersetzen, der nach jedem Aufruf einen Flush ausführt.

class Unbuffered(object):
   def __init__(self, stream):
       self.stream = stream
   def write(self, data):
       self.stream.write(data)
       self.stream.flush()
   def writelines(self, datas):
       self.stream.writelines(datas)
       self.stream.flush()
   def __getattr__(self, attr):
       return getattr(self.stream, attr)

import sys
sys.stdout = Unbuffered(sys.stdout)
print 'Hello'
Seb
quelle
71
Original sys.stdout ist weiterhin als sys .__ stdout__ verfügbar. Nur für den Fall, dass Sie es brauchen =)
Antti Rasinen
40
#!/usr/bin/env python -ufunktioniert nicht !! siehe hier
wim
6
__getattr__nur um Vererbung zu vermeiden?!
Vladimir Keleshev
32
Einige Hinweise, um Kopfschmerzen zu vermeiden: Wie ich bemerkt habe, funktioniert die Ausgabepufferung unterschiedlich, je nachdem, ob die Ausgabe an einen tty oder einen anderen Prozess / eine andere Pipe geht. Wenn es zu einem tty geht, wird es nach jedem \ n gespült , aber in einem Rohr wird es gepuffert. Im letzteren Fall können Sie diese Spüllösungen verwenden. In Cpython (nicht in pypy !!!): Wenn Sie die Eingabe mit for line in sys.stdin durchlaufen: ... sammelt die for-Schleife eine Reihe von Zeilen, bevor der Hauptteil der Schleife ausgeführt wird. Dies verhält sich wie eine Pufferung, obwohl es sich eher um eine Stapelverarbeitung handelt. Tun Sie stattdessen, während true: line = sys.stdin.readline ()
tzp
5
@tzp: Sie könnten iter()anstelle der whileSchleife verwenden : for line in iter(pipe.readline, ''):. Sie brauchen es nicht auf Python 3, wo es for line in pipe:so schnell wie möglich liefert.
JFS
77
# reopen stdout file descriptor with write mode
# and 0 as the buffer size (unbuffered)
import io, os, sys
try:
    # Python 3, open as binary, then wrap in a TextIOWrapper with write-through.
    sys.stdout = io.TextIOWrapper(open(sys.stdout.fileno(), 'wb', 0), write_through=True)
    # If flushing on newlines is sufficient, as of 3.7 you can instead just call:
    # sys.stdout.reconfigure(line_buffering=True)
except TypeError:
    # Python 2
    sys.stdout = os.fdopen(sys.stdout.fileno(), 'w', 0)

Credits: "Sebastian", irgendwo auf der Python-Mailingliste.

Federico A. Ramponi
quelle
In Python3 können Sie den Namen der Druckfunktion einfach durch einen leeren Namen überschreiben. Es ist ein schmutziger Trick!
Meawoppl
16
@meawoppl: Sie können flush=TrueParameter an print()Python seit Python 3.3 übergeben.
JFS
Das Bearbeiten der Antwort zum
Mike
beide os.fdopen(sys.stdout.fileno(), 'wb', 0)(beachten Sie die bfür binäre) und flush=Truearbeiten für mich in 3.6.4. Wenn Sie jedoch verwenden subprocess ein anderes Skript zu starten, stellen Sie sicher , haben Sie angegeben python3, wenn Sie mehrere Instanzen von Python installiert haben.
not2qubit
1
@ not2qubit: Wenn Sie verwenden, erhalten os.fdopen(sys.stdout.fileno(), 'wb', 0)Sie ein Binärdateiobjekt, keinen TextIOStream. Sie müssten TextIOWrapperder Mischung ein hinzufügen (stellen Sie sicher, dass write_throughalle Puffer entfernt werden, oder verwenden Sie line_buffering=Truediese Option , um nur Zeilenumbrüche zu löschen).
Martijn Pieters
55

Ja, so ist es.

Sie können es in der Befehlszeile mit dem Schalter "-u" deaktivieren.

Alternativ können Sie bei jedem Schreibvorgang .flush () für sys.stdout aufrufen (oder es mit einem Objekt umschließen, das dies automatisch ausführt).

Brian
quelle
19

Dies bezieht sich auf die Antwort von Cristóvão D. Sousa, aber ich konnte noch keinen Kommentar abgeben.

Eine einfache Möglichkeit, das flushSchlüsselwortargument von Python 3 zu verwenden, um immer eine ungepufferte Ausgabe zu erhalten, ist:

import functools
print = functools.partial(print, flush=True)

Danach spült der Druck die Ausgabe immer direkt (außer wenn flush=Falseangegeben).

Beachten Sie, (a) dass dies die Frage nur teilweise beantwortet, da nicht die gesamte Ausgabe umgeleitet wird. Aber ich denke, dies printist die häufigste Methode zum Erstellen von Ausgaben für stdout/ stderrin Python. Daher decken diese beiden Zeilen wahrscheinlich die meisten Anwendungsfälle ab.

Beachten Sie (b), dass es nur in dem Modul / Skript funktioniert, in dem Sie es definiert haben. Dies kann beim Schreiben eines Moduls hilfreich sein, da es nicht mit dem Modul in Konflikt gerät sys.stdout.

Python 2 liefert das flushArgument nicht, aber Sie können eine Python 3- printFunktion emulieren, wie hier beschrieben: https://stackoverflow.com/a/27991478/3734258 .

tim
quelle
1
flushNur dass es in python2 kein kwarg gibt.
o11c
@ o11c, ja du hast recht. Ich war mir sicher, dass ich es getestet habe, aber irgendwie war ich scheinbar verwirrt (: Ich habe meine Antwort geändert, hoffe, es ist jetzt in Ordnung. Danke!
Tim
14
def disable_stdout_buffering():
    # Appending to gc.garbage is a way to stop an object from being
    # destroyed.  If the old sys.stdout is ever collected, it will
    # close() stdout, which is not good.
    gc.garbage.append(sys.stdout)
    sys.stdout = os.fdopen(sys.stdout.fileno(), 'w', 0)

# Then this will give output in the correct order:
disable_stdout_buffering()
print "hello"
subprocess.call(["echo", "bye"])

Ohne das Speichern der alten sys.stdout ist disable_stdout_buffering () nicht idempotent, und mehrere Aufrufe führen zu einem Fehler wie dem folgenden:

Traceback (most recent call last):
  File "test/buffering.py", line 17, in <module>
    print "hello"
IOError: [Errno 9] Bad file descriptor
close failed: [Errno 9] Bad file descriptor

Eine andere Möglichkeit ist:

def disable_stdout_buffering():
    fileno = sys.stdout.fileno()
    temp_fd = os.dup(fileno)
    sys.stdout.close()
    os.dup2(temp_fd, fileno)
    os.close(temp_fd)
    sys.stdout = os.fdopen(fileno, "w", 0)

(Das Anhängen an gc.garbage ist keine so gute Idee, da hier unfrequente Zyklen abgelegt werden und Sie möglicherweise nach diesen suchen möchten.)

Mark Seaborn
quelle
2
Wenn das Alte stdoutnoch weiterlebt, sys.__stdout__wie einige vorgeschlagen haben, wird das Müllding nicht notwendig sein, oder? Es ist allerdings ein cooler Trick.
Thomas Ahle
1
Wie bei der Antwort von @ Federico funktioniert dies nicht mit Python 3, da ValueError: can't have unbuffered text I/Obeim Aufrufen die Ausnahme ausgelöst wird print().
Gbmhunter
Ihre "andere Möglichkeit" scheint zunächst die robusteste Lösung zu sein, leidet jedoch leider unter einer Race-Bedingung für den Fall, dass ein anderer Thread open () nach Ihrem sys.stdout.close () und vor Ihrem os.dup2 (temp_fd, fileno) aufruft ). Ich habe dies herausgefunden, als ich versucht habe, Ihre Technik unter ThreadSanitizer zu verwenden, was genau das tut. Der Fehler wird durch die Tatsache lauter, dass dup2 () mit EBUSY fehlschlägt, wenn es mit open () so läuft; siehe stackoverflow.com/questions/23440216/…
Don Hatch
13

Folgendes funktioniert in Python 2.6, 2.7 und 3.2:

import os
import sys
buf_arg = 0
if sys.version_info[0] == 3:
    os.environ['PYTHONUNBUFFERED'] = '1'
    buf_arg = 1
sys.stdout = os.fdopen(sys.stdout.fileno(), 'a+', buf_arg)
sys.stderr = os.fdopen(sys.stderr.fileno(), 'a+', buf_arg)
Gummbum
quelle
Führen Sie das zweimal aus und es stürzt unter Windows ab :-)
Michael Clerx
@MichaelClerx Mmm hmm, denken Sie immer daran, Ihre Dateien xD zu schließen.
Python 3.5 auf Raspbian 9 gibt mir OSError: [Errno 29] Illegal seekfür die Zeilesys.stdout = os.fdopen(sys.stdout.fileno(), 'a+', buf_arg)
sdbbs
12

Ja, es ist standardmäßig aktiviert. Sie können es deaktivieren, indem Sie beim Aufrufen von Python die Option -u in der Befehlszeile verwenden.

Nathan
quelle
7

Sie können Python auch mit dem Dienstprogramm stdbuf ausführen :

stdbuf -oL python <script>

Dyome
quelle
2
Die Zeilenpufferung (wie aktiviert-oL ) wird immer noch gepuffert - siehe f / e stackoverflow.com/questions/58416853/… und fragt, warum end=''die Ausgabe nicht mehr sofort angezeigt wird.
Charles Duffy
Stimmt, aber Zeilenpufferung ist die Standardeinstellung (mit einem tty). Ist es also sinnvoll, Code zu schreiben, vorausgesetzt, die Ausgabe ist vollständig ungepuffert - vielleicht besser explizit, print(..., end='', flush=True)wo dies nicht wichtig ist? OTOH: Wenn mehrere Programme gleichzeitig auf dieselbe Ausgabe schreiben, verschiebt sich der Kompromiss tendenziell vom sofortigen Fortschritt zur Reduzierung von Ausgabeverwechslungen, und die Zeilenpufferung wird attraktiv. Vielleicht ist es also besser, nicht explizit zu schreiben flushund die Pufferung extern zu steuern?
Beni Cherniavsky-Paskin
Ich denke nicht. Der Prozess selbst sollte entscheiden, wann und warum er aufruft flush. Die externe
Pufferkontrolle muss
7

In Python 3 können Sie die Druckfunktion mit einem Affen-Patch versehen, um immer Flush = True zu senden:

_orig_print = print

def print(*args, **kwargs):
    _orig_print(*args, flush=True, **kwargs)

Wie in einem Kommentar erwähnt, können Sie dies vereinfachen, indem Sie den Parameter flush an einen Wert binden, und zwar über functools.partial:

print = functools.partial(print, flush=True)
Oliver
quelle
3
Ich frage mich nur, aber wäre das nicht ein perfekter Anwendungsfall für functools.partial?
0xC0000022L
Danke @ 0xC0000022L, dadurch sieht es besser aus! print = functools.partial(print, flush=True)funktioniert gut für mich.
MarSoft
@ 0xC0000022L in der Tat, ich habe den Beitrag aktualisiert, um diese Option zu zeigen, danke für den Hinweis
Oliver
3
Wenn Sie möchten, dass das überall gilt,import builtins; builtins.print = partial(print, flush=True)
Perkins
4

Sie können auch fcntl verwenden, um die Dateiflaggen im laufenden Betrieb zu ändern.

fl = fcntl.fcntl(fd.fileno(), fcntl.F_GETFL)
fl |= os.O_SYNC # or os.O_DSYNC (if you don't care the file timestamp updates)
fcntl.fcntl(fd.fileno(), fcntl.F_SETFL, fl)
Jimx
quelle
1
Es gibt ein Windows-Äquivalent: stackoverflow.com/questions/881696/…
Tobu
12
O_SYNC hat überhaupt nichts mit der Pufferung auf Benutzerbereichsebene zu tun, nach der diese Frage fragt.
Apenwarr
4

Es ist möglich, nur die write Methode von sys.stdoutmit einer aufzurufen, die aufruft flush. Die vorgeschlagene Methodenimplementierung ist unten aufgeführt.

def write_flush(args, w=stdout.write):
    w(args)
    stdout.flush()

Der Standardwert des wArguments behält die ursprüngliche writeMethodenreferenz bei. Nachdem write_flush definiert wurde, wird das Original writemöglicherweise überschrieben.

stdout.write = write_flush

Der Code geht davon aus, dass er auf stdoutdiese Weise importiert wird from sys import stdout.

Vasily E.
quelle
3

Sie können eine ungepufferte Datei erstellen und diese Datei sys.stdout zuweisen.

import sys 
myFile= open( "a.log", "w", 0 ) 
sys.stdout= myFile

Sie können das vom System bereitgestellte Standard nicht auf magische Weise ändern. da es vom Betriebssystem an Ihr Python-Programm geliefert wird.

S.Lott
quelle
3

Variante, die ohne Absturz funktioniert (zumindest unter win32; Python 2.7, Ipython 0.12) und anschließend (mehrmals) aufgerufen wird:

def DisOutBuffering():
    if sys.stdout.name == '<stdout>':
        sys.stdout = os.fdopen(sys.stdout.fileno(), 'w', 0)

    if sys.stderr.name == '<stderr>':
        sys.stderr = os.fdopen(sys.stderr.fileno(), 'w', 0)
Laimis
quelle
Sind Sie sicher, dass dies nicht gepuffert ist?
Quanten
1
Sollten Sie prüfen, sys.stdout is sys.__stdout__anstatt sich darauf zu verlassen, dass das Ersatzobjekt ein Namensattribut hat?
Leez
Dies funktioniert hervorragend, wenn Gunicorn PYTHONUNBUFFERED aus irgendeinem Grund nicht respektiert.
Brian Arsuaga
3

(Ich habe einen Kommentar gepostet, aber er ist irgendwie verloren gegangen. Also nochmal :)

  1. Wie ich bemerkt habe, verhält sich CPython (zumindest unter Linux) unterschiedlich, je nachdem, wohin die Ausgabe geht. Wenn es zu einem tty geht, wird die Ausgabe nach jedem 'geleert. \n'
    Wenn es zu einer Pipe / einem Prozess geht, wird es gepuffert und Sie können die oben empfohlenen flush()Lösungen oder die oben empfohlene Option -u verwenden .

  2. In geringem Zusammenhang mit der Ausgabepufferung:
    Wenn Sie mit über die Zeilen in der Eingabe iterieren

    for line in sys.stdin:
    ...

Dann sammelt die for- Implementierung in CPython die Eingabe für eine Weile und führt dann den Schleifenkörper für eine Reihe von Eingabezeilen aus. Wenn Ihr Skript im Begriff ist, eine Ausgabe für jede Eingabezeile zu schreiben, sieht dies möglicherweise wie eine Ausgabepufferung aus, es wird jedoch tatsächlich gestapelt, und daher flush()hilft keine der Techniken usw. dabei. Interessanterweise haben Sie dieses Verhalten bei Pypy nicht . Um dies zu vermeiden, können Sie verwenden

while True: line=sys.stdin.readline()
...

tzp
quelle
Hier ist dein Kommentar . Es könnte ein Fehler bei älteren Python-Versionen sein. Könnten Sie Beispielcode bereitstellen? So etwas wie for line in sys.stdinvs.for line in iter(sys.stdin.readline, "")
jfs
für Zeile in sys.stdin: print ("Zeile:" + Zeile); sys.stdout.flush ()
tzp
es sieht aus wie der Read-Ahead-Fehler . Dies sollte nur unter Python 2 geschehen und wenn stdin eine Pipe ist. Der Code in meinem vorherigen Kommentar zeigt das Problem ( for line in sys.stdinbietet eine verzögerte Antwort)
jfs
2

Eine Möglichkeit, eine ungepufferte Ausgabe zu erhalten, besteht darin, sys.stderranstelle von sys.stdoutoder einfach aufzurufensys.stdout.flush() , um das Schreiben explizit zu erzwingen.

Sie können alles, was gedruckt wurde, leicht umleiten, indem Sie Folgendes tun:

import sys; sys.stdout = sys.stderr
print "Hello World!"

Oder um nur für eine bestimmte printAnweisung umzuleiten :

print >>sys.stderr, "Hello World!"

Um stdout zurückzusetzen, können Sie einfach Folgendes tun:

sys.stdout = sys.__stdout__
stderr
quelle
1
Dies kann sehr verwirrend werden, wenn Sie später versuchen, die Ausgabe mithilfe der Standardumleitung zu erfassen, und feststellen, dass Sie nichts erfassen! ps dein stdout wird fett und so.
Freiraum
1
Eine große Vorsicht beim selektiven Drucken auf stderr besteht darin, dass die Zeilen dadurch nicht richtig angezeigt werden. Wenn Sie also keinen Zeitstempel haben, kann dies sehr verwirrend werden.
Haridsv