2012-10-08 83 views
39

NB: No he intentado reproducir el problema descrito debajo en Windows, o con versiones de Python que no sean 2.7.3.Cómo silenciar el error "sys.excepthook is missing"?

La forma más confiable para obtener el problema en cuestión es canalizar la salida de la siguiente secuencia de comandos de prueba a través : (bajo bash):

try: 
    for n in range(20): 
     print n 
except: 
    pass 

Es decir:

% python testscript.py | : 
close failed in file object destructor: 
sys.excepthook is missing 
lost sys.stderr 

Mi pregunta es :

How can I modify the test script above to avoid the error message when the script is run as shown (under Unix/ bash)?

(Como muestra el script de prueba, el error no puede ser atrapado con un try-except.)

El ejemplo anterior es, ciertamente, muy artificial, pero me estoy quedando en el mismo problema veces cuando la salida de un guión mío se canaliza a través de alguna tercera parte software.

El mensaje de error es ciertamente inofensivo, pero desconcierta a los usuarios finales, por lo que me gustaría silenciarlo.

EDITAR: La siguiente secuencia de comandos, que difiere de la original anterior solo en que redefine sys.excepthook, se comporta exactamente como la anterior.

import sys 
STDERR = sys.stderr 
def excepthook(*args): 
    print >> STDERR, 'caught' 
    print >> STDERR, args 

sys.excepthook = excepthook 

try: 
    for n in range(20): 
     print n 
except: 
    pass 

Respuesta

60

How can I modify the test script above to avoid the error message when the script is run as shown (under Unix/ bash)?

Usted tendrá que evitar que el guión de escribir cualquier cosa con la norma salida. Eso significa eliminar todas las declaraciones print y cualquier uso de sys.stdout.write, así como cualquier código que los llame.

La razón por la que esto está sucediendo es que está canalizando una cantidad de salida diferente de cero de su secuencia de comandos de Python a algo que nunca se lee desde la entrada estándar. Esto no es exclusivo del comando :; se puede obtener el mismo resultado mediante la canalización a cualquier comando que no lee la entrada estándar, tales como

python testscript.py | cd . 

O para un ejemplo simple, considere un guión nada printer.py que contiene más de

print 'abcde' 

Entonces

python printer.py | python printer.py 

producirá el mismo error.

Cuando canaliza la salida de un programa a otro, la salida producida por el programa de escritura queda respaldada en un búfer y espera a que el programa de lectura solicite esa información del búfer. Siempre que el búfer no esté vacío, se supone que cualquier intento de cerrar el objeto del archivo de escritura falla con un error. Esta es la causa raíz de los mensajes que está viendo.

El código específico que desencadena el error está en la implementación del lenguaje C de Python, lo que explica por qué no puede detectarlo con un bloque try/except: se ejecuta después de que el contenido de su script ha terminado de procesarse. Básicamente, mientras Python se está cerrando, intenta cerrar stdout, pero eso falla porque aún hay salida almacenada en búfer en espera de ser leída. Así que Python intenta informar este error como lo haría normalmente, pero sys.excepthook ya se ha eliminado como parte del procedimiento de finalización, por lo que falla. Python luego intenta imprimir un mensaje al sys.stderr, pero eso ya ha sido desasignado así que de nuevo, falla. La razón por la que ve los mensajes en la pantalla es que el código de Python contiene una contingencia fprintf para escribir directamente alguna salida en el puntero del archivo, incluso si el objeto de salida de Python no existe.

Detalles técnicos

Para aquellos interesados ​​en los detalles de este procedimiento, vamos a echar un vistazo a la secuencia de apagado del intérprete de Python, que se implementa en el Py_Finalize function de pythonrun.c.

  1. Después de invocar ganchos de salida y el cierre de las discusiones, el código de finalización llama PyImport_Cleanup para finalizar y desasignar todos los módulos importados. La penúltima tarea realizada por esta función es removing the sys module, que consiste principalmente en llamar al _PyModule_Clear para borrar todas las entradas en el diccionario del módulo, incluidos, en particular, los objetos de transmisión estándar (los objetos de Python) como stdout y stderr.
  2. Cuando se elimina un valor de un diccionario o se reemplaza por un nuevo valor, its reference count is decremented utilizando the Py_DECREF macro. Los objetos cuyo recuento de referencias llega a cero son elegibles para la desasignación. Como el módulo sys contiene las últimas referencias restantes a los objetos de flujo estándar, cuando esas referencias son desarmadas por _PyModule_Clear, entonces están listas para ser desasignadas.
  3. La desasignación de un objeto de archivo Python se realiza por the file_dealloc function en fileobject.c. Esta primera invokes the Python file object's close method utilizando la bien-nombrado close_the_file function:

    ret = close_the_file(f); 
    

    Para un objeto de archivo estándar, close_the_file(f)delegates to the C fclose function, que establece una condición de error si hay datos que se escriben en el puntero del archivo todavía. file_dealloc comprueba entonces para esa condición de error y muestra el primer mensaje que aparece:

    if (!ret) { 
        PySys_WriteStderr("close failed in file object destructor:\n"); 
        PyErr_Print(); 
    } 
    else { 
        Py_DECREF(ret); 
    } 
    
  4. Después de imprimir este mensaje, Python y luego intenta mostrar la excepción usando PyErr_Print. Eso delega en PyErr_PrintEx, y como parte de su funcionalidad, PyErr_PrintEx intenta acceder a la impresora de excepción de Python desde sys.excepthook.

    hook = PySys_GetObject("excepthook"); 
    

    Esto estaría bien si se hace en el curso normal de un programa en Python, pero en esta situación, sys.excepthook ya se ha despejado. Python comprueba esta condición de error e imprime el segundo mensaje como una notificación.

    if (hook && hook != Py_None) { 
        ... 
    } else { 
        PySys_WriteStderr("sys.excepthook is missing\n"); 
        PyErr_Display(exception, v, tb); 
    } 
    
  5. Después de avisarnos acerca de la falta excepthook, Python y luego cae de nuevo a imprimir la información de excepciones mediante PyErr_Display, que es el método por defecto para mostrar un seguimiento de la pila. Lo primero que hace esta función es intentar acceder al sys.stderr.

    PyObject *f = PySys_GetObject("stderr"); 
    

    En este caso, que no funciona porque sys.stderr ya se ha despejado y es inaccesible. El código invoca fprintf directamente para enviar el tercer mensaje a la secuencia de error estándar C.

    if (f == NULL || f == Py_None) 
        fprintf(stderr, "lost sys.stderr\n"); 
    

Curiosamente, el comportamiento es un poco diferente en Python 3.4+ debido a que el procedimiento de finalización ahora explicitly flushes the standard output and error streams antes se borran los módulos incorporadas. De esta manera, si tiene datos que esperan ser escritos, se obtiene un error que señala explícitamente esa condición, en lugar de una falla "accidental" en el procedimiento normal de finalización. Además, si ejecuta

python printer.py | python printer.py 

usando Python 3.4 (después de poner paréntesis en la declaración print por supuesto), no obtiene ningún error en absoluto. Supongo que la segunda invocación de Python puede estar consumiendo entrada estándar por alguna razón, pero eso es un problema completamente diferente.


En realidad, eso es una mentira. El mecanismo de importación de Python caches a copy of each imported module's dictionary, que no se lanza hasta _PyImport_Fini se ejecuta, later in the implementation of Py_Finalize y que es cuando desaparecen las últimas referencias a los objetos de transmisión estándar. Una vez que el recuento de referencia llega a cero, Py_DECREF desasigna los objetos inmediatamente. Pero todo lo que importa para la respuesta principal es que las referencias se eliminan del diccionario del módulo sys y luego se desasignan un tiempo después.

Nuevamente, esto se debe a que el diccionario del módulo sys se borra por completo antes de que se desasigne realmente algo, gracias al mecanismo de caché de atributos. Puede ejecutar Python con la opción -vv para ver cómo se desarman todos los atributos del módulo antes de recibir el mensaje de error sobre el cierre del puntero al archivo.

Este comportamiento particular es la única parte que no tiene sentido a menos que sepa sobre el mecanismo de caché de atributos mencionado en notas anteriores.

+0

Una explicación muy lúcida y concisa de algo que normalmente me obligaría a buscar un tanque de oxígeno (¡ahogo, necesito más aire!), ¡Gracias! –

+0

En el caso de que otros programas necesiten el resultado de la secuencia de comandos python, p. grep, ¿hay alguna forma mejor de implementar que 'python printer.py | grep "abc" '? –

+0

@MarkZ. Honestamente, la mejor solución es no canalizar la salida de la secuencia de comandos de Python a un programa que no lo lee, es decir, evitar toda la situación que provocó esta pregunta en primer lugar. Si eso no es posible por alguna extraña razón, puede implementar una opción de línea de comandos como '--quiet' o' --silent' que suprimirá todos los resultados del script de Python. –

1

En su programa se arroja una excepción que no se puede capturar usando try/except block. Para la captura de él, función de anulación sys.excepthook:

import sys 
sys.excepthook = lambda *args: None 

De documentation:

sys.excepthook(type, value, traceback)

When an exception is raised and uncaught, the interpreter calls sys.excepthook with three arguments, the exception class, exception instance, and a traceback object. In an interactive session this happens just before control is returned to the prompt; in a Python program this happens just before the program exits. The handling of such top-level exceptions can be customized by assigning another three-argument function to sys.excepthook.

Ejemplo ilustrativo:

import sys 
import logging 

def log_uncaught_exceptions(exception_type, exception, tb): 

    logging.critical(''.join(traceback.format_tb(tb))) 
    logging.critical('{0}: {1}'.format(exception_type, exception)) 

sys.excepthook = log_uncaught_exceptions 
+1

¿Se intenta *** * ** esta solución con el script de prueba que proporcioné? Después de todo, esa fue la razón por la que lo proporcioné ... – kjo

10

Me encontré hoy con este tipo de problema y fui en busca de una respuesta. Creo que una solución simple aquí es garantizar que primero descargue stdio, por lo que Python bloquea en lugar de fallar durante el apagado del script.Por ejemplo:

--- a/testscript.py 
+++ b/testscript.py 
@@ -9,5 +9,6 @@ sys.excepthook = excepthook 
try: 
    for n in range(20): 
     print n 
+ sys.stdout.flush() 
except: 
    pass 

Entonces, con este script no pasa nada, ya que la excepción (IOError: [Errno 32] Broken pipe) es suprimida por el try ... except.

$ python testscript.py | : 
$ 
-3

Me doy cuenta de que esta es una pregunta antigua, pero la encontré en una búsqueda en Google del error. En mi caso, fue un error de codificación. Una de mis últimas declaraciones fue:

print "Good Bye" 

La solución fue simplemente la fijación de la sintaxis para:

print ("Good Bye") 

[Frambuesa Pi cero, Python 2.7.9]

+1

En Python 2.7.9, no hay diferencia entre tener paréntesis en 'print' y no. – DyZ