Frage: Gibt es eine Möglichkeit, flush=True
für die print()
-Funktion zu verwenden, ohne die BrokenPipeError
zu erhalten?
Ich habe ein Skript pipe.py
:
for i in range(4000):
print(i)
Ich nenne es von einer Unix-Kommandozeile aus so:
python3 pipe.py | head -n3000
Und es kehrt zurück:
0
1
2
So funktioniert dieses Skript:
import sys
for i in range(4000):
print(i)
sys.stdout.flush()
Wenn ich dieses Skript jedoch ausführe und es an head -n3000
weiterleite:
for i in range(4000):
print(i, flush=True)
Dann bekomme ich diesen Fehler:
print(i, flush=True)
BrokenPipeError: [Errno 32] Broken pipe
Exception BrokenPipeError: BrokenPipeError(32, 'Broken pipe') in <_io.TextIOWrapper name='<stdout>' mode='w' encoding='UTF-8'> ignored
Ich habe auch die folgende Lösung ausprobiert, erhalte aber immer noch die BrokenPipeError
:
import sys
for i in range(4000):
try:
print(i, flush=True)
except BrokenPipeError:
sys.exit()
Das BrokenPipeError
ist wie das genannte Phantom normal, da der Lesevorgang (head) beendet und sein Ende der Pipe schließt, während der Schreibvorgang (python) noch versucht zu schreiben.
Is ist ein abnormaler Zustand, und das python scripts empfängt ein BrokenPipeError
- genauer gesagt, das Python Interpreter empfängt ein System-SIGPIPE-Signal, das er abfängt und das BrokenPipeError
auslöst, damit das Skript den Fehler verarbeiten kann.
Und Sie können den Fehler effektiv verarbeiten, da in Ihrem letzten Beispiel nur die Meldung angezeigt wird, dass die Ausnahme ignoriert wurde - ok, das ist nicht wahr, scheint aber mit diesem verbunden zu sein offenes Problem in Python: Python= Entwickler halten es für wichtig, den Benutzer vor dem abnormalen Zustand zu warnen.
Was wirklich passiert, ist, dass AFAIK, der python= - Interpreter, dies immer auf stderr signalisiert, auch wenn Sie die Ausnahme abfangen. Sie müssen jedoch stderr vor dem Beenden schließen, um die Nachricht loszuwerden.
Ich habe dein Skript leicht geändert in:
Hier ist das Skript, das ich verwendet habe:
import sys
try:
for i in range(4000):
print(i, flush=True)
except (BrokenPipeError, IOError):
print ('BrokenPipeError caught', file = sys.stderr)
print ('Done', file=sys.stderr)
sys.stderr.close()
und hier das Ergebnis von python3.3 pipe.py | head -10
:
0
1
2
3
4
5
6
7
8
9
BrokenPipeError caught
Done
Wenn Sie die fremden Nachrichten nicht möchten, verwenden Sie einfach:
import sys
try:
for i in range(4000):
print(i, flush=True)
except (BrokenPipeError, IOError):
pass
sys.stderr.close()
Gemäß der Python-Dokumentation wird dies ausgelöst, wenn:
versucht, auf eine Pipe zu schreiben, während das andere Ende geschlossen wurde
Dies liegt an der Tatsache, dass das Dienstprogramm head aus stdout
liest, und es dann prompt schließt .
Wie Sie sehen, können Sie es umgehen, indem Sie nach jeder sys.stdout.flush()
einfach eine print()
hinzufügen. Beachten Sie, dass dies in Python 3 manchmal nicht funktioniert.
Sie können es alternativ an awk
weiterleiten, um dasselbe Ergebnis wie head -3
zu erhalten:
python3 0to3.py | awk 'NR >= 4 {exit} 1'
Hoffe das hat geholfen, viel Glück!
Wie Sie in der von Ihnen geposteten Ausgabe sehen können, wird die letzte Ausnahme in der Destruktorphase ausgelöst: Deshalb haben Sie am Ende ignored
Exception BrokenPipeError: BrokenPipeError(32, 'Broken pipe') in <_io.TextIOWrapper name='<stdout>' mode='w' encoding='UTF-8'> ignored
Ein einfaches Beispiel, um zu verstehen, was in diesem Zusammenhang vor sich geht, ist folgendes:
>> class A():
... def __del__(self):
... raise Exception("It will be ignored!!!")
...
>>> a = A()
>>> del a
Exception Exception: Exception('It will be ignored!!!',) in <bound method A.__del__ of <__builtin__.A instance at 0x7ff1d5c06d88>> ignored
>>> a = A()
>>> import sys
>>> sys.stderr.close()
>>> del a
Jede Ausnahme, die ausgelöst wird, während das Objekt zerstört wird, führt zu einer Standardfehlerausgabe, in der erklärt wird, dass die Ausnahme aufgetreten und ignoriert wurde (Python informiert Sie darüber, dass in der Zerstörungsphase etwas nicht korrekt verarbeitet werden kann). Diese Art von Ausnahmen kann jedoch nicht zwischengespeichert werden. Sie können also einfach die Aufrufe entfernen, die sie generieren können, oder stderr
schließen.
Komm zurück zu der Frage. Diese Ausnahme ist kein echtes Problem (wie gesagt, sie wird ignoriert), aber wenn Sie sie nicht drucken möchten, müssen Sie die Funktion überschreiben, die aufgerufen werden kann, wenn das Objekt zerstört wird, oder stderr
schließen, wie @SergeBallesta richtig vorgeschlagen: in Ihnen In diesem Fall können Sie die Funktion shutdown write
und flush
verwenden und es wird keine Ausnahme im Kontext von delete ausgelöst
Das ist ein Beispiel dafür, wie Sie es schaffen können:
import sys
def _void_f(*args,**kwargs):
pass
for i in range(4000):
try:
print(i,flush=True)
except (BrokenPipeError, IOError):
sys.stdout.write = _void_f
sys.stdout.flush = _void_f
sys.exit()
Ich habe mir oft gewünscht, es gäbe eine Befehlszeilenoption, um diese Signalhandler zu unterdrücken.
import signal
# Don't turn these signal into exceptions, just die.
signal.signal(signal.SIGINT, signal.SIG_DFL)
signal.signal(signal.SIGPIPE, signal.SIG_DFL)
Stattdessen können wir die Handler so schnell wie möglich deinstallieren, sobald das Skript Python ausgeführt wird.
SIGPPIE vorübergehend ignorieren
Ich bin nicht sicher, wie schlecht eine Idee ist, aber es funktioniert:
#!/usr/bin/env python3
import signal
import sys
sigpipe_old = signal.getsignal(signal.SIGPIPE)
signal.signal(signal.SIGPIPE, signal.SIG_DFL)
for i in range(4000):
print(i, flush=True)
signal.signal(signal.SIGPIPE, sigpipe_old)
Während andere das zugrunde liegende Problem ausführlich behandelt haben, gibt es eine einfache Problemumgehung:
python whatever.py | tail -n +1 | head -n3000
Erläuterung: tail
puffert, bis STDIN geschlossen ist (Python beendet und schließt STDOUT). So bekommt nur der Schwanz die Sigpipe, wenn der Kopf aufhört. Das -n +1
ist praktisch ein No-Op, bei dem die Tail-Ausgabe das "Tail" ab Zeile 1 ist, bei dem es sich um den gesamten Puffer handelt.