Pregunta: ¿Hay alguna forma de usar flush=True
para la función print()
sin obtener BrokenPipeError
?
Tengo un script pipe.py
:
for i in range(4000): print(i)
Lo llamo así desde una línea de comando de Unix:
python3 pipe.py | head -n3000
Y vuelve:
0 1 2
Así lo hace este script:
import sys for i in range(4000): print(i) sys.stdout.flush()
Sin embargo, cuando ejecuto este script y lo head -n3000
a head -n3000
:
for i in range(4000): print(i, flush=True)
Entonces me sale este error:
print(i, flush=True) BrokenPipeError: [Errno 32] Broken pipe Exception BrokenPipeError: BrokenPipeError(32, 'Broken pipe') in <_io.TextIOWrapper name='' mode='w' encoding='UTF-8'> ignored
También probé la solución a continuación, pero aún así obtengo el BrokenPipeError
:
import sys for i in range(4000): try: print(i, flush=True) except BrokenPipeError: sys.exit()
El BrokenPipeError
es normal como dicho fantasma porque el proceso de lectura (cabeza) termina y cierra su extremo de la tubería mientras el proceso de escritura (python) aún intenta escribir.
Es una condición anormal, y los scripts de Python reciben un BrokenPipeError
; más exactamente, el intérprete de Python recibe una señal SIGPIPE del sistema que BrokenPipeError
y genera el BrokenPipeError
para permitir que el script procese el error.
Y efectivamente puede procesar el error, porque en su último ejemplo, solo ve un mensaje que dice que se ignoró la excepción. Está bien, no es cierto, pero parece estar relacionado con este problema abierto en Python: los desarrolladores de Python piensan que es importante advertir al usuario La condición anormal.
Lo que realmente sucede es que AFAIK, el intérprete de python, siempre señala esto en stderr, incluso si detecta la excepción. Pero solo tienes que cerrar stderr antes de salir para deshacerte del mensaje.
Cambié un poco tu guión a:
Aquí está el guión que utilicé:
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()
Y aquí el resultado de python3.3 pipe.py | head -10
python3.3 pipe.py | head -10
:
0 1 2 3 4 5 6 7 8 9 BrokenPipeError caught Done
Si no desea que los mensajes extraños solo use:
import sys try: for i in range(4000): print(i, flush=True) except (BrokenPipeError, IOError): pass sys.stderr.close()
Según la documentación de Python, esto se lanza cuando:
tratando de escribir en una tubería mientras el otro extremo ha sido cerrado
Esto se debe al hecho de que la utilidad principal se lee desde la stdout
, a continuación, la cierra rápidamente .
Como puede ver, puede sys.stdout.flush()
simplemente agregando un sys.stdout.flush()
después de cada print()
. Tenga en cuenta que esto a veces no funciona en Python 3.
Alternativamente, puede canalizarlo a awk
esta manera para obtener el mismo resultado que head -3
:
python3 0to3.py | awk 'NR >= 4 {exit} 1'
Espero que esto haya ayudado, buena suerte!
Como se puede ver en la salida, la última excepción se generó en la fase del destructor: es por eso que se ignored
al final.
Exception BrokenPipeError: BrokenPipeError(32, 'Broken pipe') in <_io.TextIOWrapper name='' mode='w' encoding='UTF-8'> ignored
Un ejemplo simple para entender qué hay en ese contexto es el siguiente:
>> class A(): ... def __del__(self): ... raise Exception("It will be ignored!!!") ... >>> a = A() >>> del a Exception Exception: Exception('It will be ignored!!!',) in > ignored >>> a = A() >>> import sys >>> sys.stderr.close() >>> del a
Todas las excepciones que se activan mientras se destruye el objeto causarán un error estándar que explica que la excepción ocurrió e ignorada (es decir, porque Python le informará que algo no se pudo manejar correctamente en la fase de destrucción). De todos modos, ese tipo de excepciones no se pueden almacenar en caché y, por lo tanto, solo puede eliminar las llamadas que puedan generarlo o cerrar stderr
.
Vuelve a la pregunta. Esa excepción no es un problema real (como se dice que se ignora), pero si no desea imprimirla, debe anular la función a la que se puede llamar cuando se destruirá el objeto o cerrar stderr
como @SergeBallesta sugirió correctamente: en usted En caso de que pueda apagar la función de write
y flush
, no se activará ninguna excepción en el contexto de destrucción
Ese es un ejemplo de cómo puedes hacerlo:
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()
Ignorar SIGPPIE temporalmente
No estoy seguro de cuán mala es esta idea, pero funciona:
#!/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)