it-swarm.com.de

Wie kann ich BrokenPipeError beim Flush in Python verhindern?

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()
20

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:

  • fangen Sie den Fehler wie in Ihrem letzten Beispiel ab
  • fange entweder IOError (das bekomme ich in Python34 unter Windows64) oder BrokenPipeError (in Python 33 unter FreeBSD 9.0)) - und zeige eine Meldung dazu an
  • zeige eine benutzerdefinierte Done Nachricht auf stderr an (stdout ist wegen der kaputten Pipe geschlossen)
  • Schließen Sie stderr , bevor Sie das Programm verlassen, um die Nachricht zu löschen

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()
25
Serge Ballesta

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!

4
phantom

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()
1
Michele d'Amico

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.

0
Waxrat

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.

0
notpeter