Question: Existe-t-il un moyen d'utiliser flush=True
pour la fonction print()
sans obtenir le BrokenPipeError
?
J'ai un script pipe.py
:
for i in range(4000):
print(i)
J'appelle ça comme ça depuis une ligne de commande Unix:
python3 pipe.py | head -n3000
Et ça revient:
0
1
2
Ainsi fait ce script:
import sys
for i in range(4000):
print(i)
sys.stdout.flush()
Cependant, lorsque j'exécute ce script et le transfère sur head -n3000
:
for i in range(4000):
print(i, flush=True)
Puis j'obtiens cette erreur:
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
J'ai aussi essayé la solution ci-dessous, mais j'obtiens toujours la BrokenPipeError
:
import sys
for i in range(4000):
try:
print(i, flush=True)
except BrokenPipeError:
sys.exit()
La BrokenPipeError
est normale en tant que dit fantôme parce que le processus de lecture (tête) se termine et ferme son extrémité du tuyau tandis que le processus d'écriture (python) tente toujours d'écrire.
Est-ce que est une condition anormale et que les scripts python reçoivent un BrokenPipeError
- plus exactement, l'interpréteur Python reçoit un signal système SIGPIPE qu'il attrape et soulève le BrokenPipeError
pour permettre au script de traiter l'erreur.
Et vous pouvez effectivement traiter l'erreur, car dans votre dernier exemple, vous ne voyez qu'un message disant que l'exception a été ignorée - ok, ce n'est pas vrai, mais semble liée à cette question ouverte dans Python: les développeurs Python pensent important avertir l'utilisateur de la condition anormale.
Ce qui se passe réellement, c’est que, autant que je sache, l’interprète python le signale toujours sur stderr, même si vous attrapez l’exception. Mais il vous suffit de fermer stderr avant de quitter pour vous débarrasser du message.
J'ai légèrement changé votre script en:
Voici le script que j'ai utilisé:
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()
et voici le résultat de python3.3 pipe.py | head -10
:
0
1
2
3
4
5
6
7
8
9
BrokenPipeError caught
Done
Si vous ne voulez pas que les messages superflus, utilisez simplement:
import sys
try:
for i in range(4000):
print(i, flush=True)
except (BrokenPipeError, IOError):
pass
sys.stderr.close()
Selon la documentation Python, ceci est levé quand:
essayer d'écrire sur un tuyau alors que l'autre extrémité est fermée
Cela est dû au fait que l'utilitaire principal lit stdout
, puis le ferme promptement .
Comme vous pouvez le constater, il est possible de contourner le problème en ajoutant simplement une sys.stdout.flush()
après chaque print()
. Notez que cela ne fonctionne parfois pas dans Python 3.
Vous pouvez également le diriger vers awk
comme ceci pour obtenir le même résultat que head -3
:
python3 0to3.py | awk 'NR >= 4 {exit} 1'
J'espère que cela vous a aidé, bonne chance!
Comme vous pouvez le voir dans le résultat que vous avez publié, la dernière exception est déclenchée dans la phase de destruction: c'est pourquoi vous avez ignored
à la fin.
Exception BrokenPipeError: BrokenPipeError(32, 'Broken pipe') in <_io.TextIOWrapper name='<stdout>' mode='w' encoding='UTF-8'> ignored
Voici un exemple simple pour comprendre ce qui se passe dans ce contexte:
>> 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
Chaque exception déclenchée lors de la destruction de l'objet provoquera une sortie d'erreur standard expliquant que l'exception s'est produite et qu'elle a été ignorée (car python vous informera que quelque chose ne peut pas être correctement géré en phase de destruction). Quoi qu'il en soit, ce type d'exceptions ne peut pas être mis en cache, vous pouvez donc simplement supprimer les appels qui peuvent le générer ou fermer stderr
.
Revenez à la question. Cette exception n’est pas un réel problème (comme elle est ignorée) mais si vous ne voulez pas l’imprimer, vous devez remplacer la fonction qui peut être appelée lorsque l’objet sera détruit ou fermer stderr
comme @SergeBallesta l’a bien suggéré si vous pouvez shutdown write
et flush
et qu'aucune exception ne sera déclenchée dans le contexte de destruction
C'est un exemple de la façon dont vous pouvez le faire:
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()
Ignorer SIGPPIE temporairement
Je ne suis pas sûr que ce soit une mauvaise idée, mais ça marche:
#!/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)