¿Cómo evitar BrokenPipeError cuando se realiza un lavado en Python?

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:

  • atrapa el error como hiciste en tu último ejemplo
  • atrapa IOError (que obtengo en Python34 en Windows64) o BrokenPipeError (en Python 33 en FreeBSD 9.0) – y muestra un mensaje para eso
  • mostrar un mensaje personalizado hecho en stderr (stdout está cerrado debido a la tubería rota)
  • cierre stderr antes de salir para deshacerse del mensaje

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)