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
.
- 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
.
- 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.
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);
}
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);
}
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.
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! –
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" '? –
@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. –