NB: Je n'ai pas tenté de reproduire le problème décrit ci-dessous sous Windows, ou avec des versions de Python autres que 2.7.3.
Le moyen le plus fiable pour résoudre le problème en question consiste à diriger la sortie du script de test suivant via :
(sous bash
):
try:
for n in range(20):
print n
except:
pass
C'est à dire.:
% python testscript.py | :
close failed in file object destructor:
sys.excepthook is missing
lost sys.stderr
Ma question est:
Comment puis-je modifier le script de test ci-dessus pour éviter le message d'erreur lorsque le script est exécuté comme indiqué (sous Unix/
bash
)?
(Comme le montre le script de test, l'erreur ne peut pas être interceptée avec un try-except
.)
L'exemple ci-dessus est, certes, très artificiel, mais je rencontre le même problème parfois lorsque la sortie d'un de mes scripts est acheminée via un logiciel tiers.
Le message d'erreur est certainement inoffensif, mais il est déconcertant pour les utilisateurs finaux, je voudrais donc le faire taire.
EDIT: Le script suivant, qui diffère de l'original ci-dessus uniquement en ce qu'il redéfinit sys.excepthook, se comporte exactement comme celui donné ci-dessus.
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
Comment puis-je modifier le script de test ci-dessus pour éviter le message d'erreur lorsque le script est exécuté comme indiqué (sous Unix/
bash
)?
Vous devrez empêcher le script d'écrire quoi que ce soit sur la sortie standard. Cela signifie supprimer toutes les instructions print
et toute utilisation de sys.stdout.write
, Ainsi que tout code qui les appelle.
La raison pour laquelle cela se produit est que vous envoyez une quantité de sortie non nulle de votre script Python à quelque chose qui ne lit jamais à partir de l'entrée standard. Ce n'est pas unique au :
; vous pouvez obtenir le même résultat en redirigeant vers n'importe quelle commande qui ne lit pas l'entrée standard, telle que
python testscript.py | cd .
Ou pour un exemple plus simple, considérez un script printer.py
Ne contenant rien de plus que
print 'abcde'
Ensuite
python printer.py | python printer.py
produira la même erreur.
Lorsque vous dirigez la sortie d'un programme dans un autre, la sortie produite par le programme d'écriture est sauvegardée dans un tampon et attend que le programme de lecture demande ces données au tampon. Tant que le tampon n'est pas vide, toute tentative de fermeture de l'objet fichier d'écriture est censée échouer avec une erreur. C'est la cause première des messages que vous voyez.
Le code spécifique qui déclenche l'erreur se trouve dans l'implémentation en langage C de Python, ce qui explique pourquoi vous ne pouvez pas l'attraper avec un bloc try
/except
: il s'exécute après le contenu de votre script a terminé le traitement. Fondamentalement, alors que Python se ferme, il tente de fermer stdout
, mais cela échoue car il y a toujours une sortie en mémoire tampon en attente de lecture. Donc Python essaie de signaler cette erreur comme il le ferait normalement, mais sys.excepthook
a déjà été supprimé dans le cadre de la procédure de finalisation, ce qui échoue. Python essaie ensuite d'imprimer un message à sys.stderr
, mais qui a déjà été désalloué à nouveau, il échoue. La raison pour laquelle vous voyez les messages à l'écran est que le code Python contient une contingence fprintf
pour écrire directement une sortie dans le pointeur de fichier, même si l'objet de sortie de Python n'existe pas.
Pour ceux qui s'intéressent aux détails de cette procédure, jetons un œil à la séquence d'arrêt de l'interpréteur Python, qui est implémentée dans la fonction Py_Finalize
de pythonrun.c
.
PyImport_Cleanup
pour finaliser et désallouer tous les modules importés. L'avant-dernière tâche effectuée par cette fonction est suppression du module sys
, qui consiste principalement à appeler _PyModule_Clear
pour tout effacer les entrées dans le dictionnaire du module - y compris, en particulier, les objets de flux standard (les objets Python) tels que stdout
et stderr
.Py_DECREF
. Les objets dont le nombre de références atteint zéro deviennent éligibles à la désallocation. Étant donné que le module sys
contient les dernières références restantes aux objets de flux standard, lorsque ces références ne sont pas définies par _PyModule_Clear
, Elles sont alors prêtes à être désallouées.1La désallocation d'un Python est effectuée par la fonction file_dealloc
dans fileobject.c
. Cette première invoque le = Python de l'objet fichier close
en utilisant la dénomination appropriée fonction close_the_file
:
ret = close_the_file(f);
Pour un objet fichier standard, close_the_file(f)
délègue à la fonction C fclose
, qui définit une condition d'erreur s'il reste des données à écrire dans le pointeur de fichier. file_dealloc
Vérifie ensuite cette condition d'erreur et imprime le premier message que vous voyez:
if (!ret) {
PySys_WriteStderr("close failed in file object destructor:\n");
PyErr_Print();
}
else {
Py_DECREF(ret);
}
Après avoir imprimé ce message, Python essaie ensuite d'afficher l'exception en utilisant PyErr_Print
. Cela délègue à PyErr_PrintEx
, et dans le cadre de ses fonctionnalités, PyErr_PrintEx
tente d'accéder à l'imprimante d'exception Python à partir de sys.excepthook
).
hook = PySys_GetObject("excepthook");
Ce serait bien si cela était fait dans le cours normal d'un programme Python, mais dans cette situation, sys.excepthook
A déjà été effacé.2 Python vérifie cette condition d'erreur et imprime le deuxième message sous forme de notification.
if (hook && hook != Py_None) {
...
} else {
PySys_WriteStderr("sys.excepthook is missing\n");
PyErr_Display(exception, v, tb);
}
Après nous avoir informés du excepthook
manquant, Python revient ensuite à l'impression des informations d'exception à l'aide de PyErr_Display
, qui est la valeur par défaut pour afficher une trace de pile. La toute première chose que cette fonction fait est d'essayer d'accéder à sys.stderr
.
PyObject *f = PySys_GetObject("stderr");
Dans ce cas, cela ne fonctionne pas car sys.stderr
A déjà été effacé et est inaccessible.3 Le code invoque donc fprintf
directement pour envoyer le troisième message au flux d'erreur standard C.
if (f == NULL || f == Py_None)
fprintf(stderr, "lost sys.stderr\n");
Fait intéressant, le comportement est un peu différent dans Python 3.4+ parce que la procédure de finalisation maintenant vide explicitement la sortie standard et les flux d'erreur avant que les modules intégrés ne soient effacés. De cette façon, si vous avez des données en attente d'écriture, vous obtenez une erreur qui signale explicitement cette condition, plutôt qu'une défaillance "accidentelle" dans la procédure de finalisation normale. De plus, si vous exécutez
python printer.py | python printer.py
en utilisant Python 3.4 (après avoir mis des parenthèses sur l'instruction print
bien sûr), vous n'obtenez aucune erreur. Je suppose que la deuxième invocation de Python peut consommer des entrées standard pour une raison quelconque, mais c'est un problème complètement différent.
1En fait, c'est un mensonge. Mécanisme d'importation de Python met en cache une copie du dictionnaire de chaque module importé , qui n'est pas publié avant _PyImport_Fini
s'exécute, plus tard dans l'implémentation de Py_Finalize
, et c'est lorsque les dernières références aux objets de flux standard disparaissent. Une fois que le nombre de références atteint zéro, Py_DECREF
Désalloue immédiatement les objets . Mais tout ce qui compte pour la réponse principale est que les références sont supprimées du dictionnaire du module sys
puis désallouées un peu plus tard.
2Encore une fois, cela est dû au fait que le dictionnaire du module sys
est complètement effacé avant que quoi que ce soit ne soit vraiment désalloué, grâce au mécanisme de mise en cache des attributs. Vous pouvez exécuter Python avec l'option -vv
Pour voir tous les attributs du module non définis avant d'obtenir le message d'erreur sur la fermeture du pointeur de fichier.
3Ce comportement particulier est la seule partie qui n'a de sens que si vous connaissez le mécanisme de mise en cache des attributs mentionné dans les notes de bas de page précédentes.
J'ai rencontré ce genre de problème moi-même aujourd'hui et je suis allé chercher une réponse. Je pense qu'une solution de contournement simple ici est de vous assurer de vider stdio en premier, donc python bloque au lieu d'échouer pendant l'arrêt du script. Par exemple:
--- 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
Ensuite, avec ce script, rien ne se passe, car l'exception (IOError: [Errno 32] Pipe cassée) est supprimée par la tentative ... sauf.
$ python testscript.py | :
$
Dans votre programme lève une exception qui ne peut pas être interceptée en utilisant le bloc try/except. Pour l'attraper, remplacez la fonction sys.excepthook
:
import sys
sys.excepthook = lambda *args: None
De documentation :
sys.excepthook (type, valeur, traceback)
Lorsqu'une exception est levée et non interceptée, l'interpréteur appelle sys.excepthook avec trois arguments, la classe d'exception, l'instance d'exception et un objet traceback. Dans une session interactive, cela se produit juste avant le retour du contrôle à l'invite; dans un programme Python cela se produit juste avant la fin du programme. La gestion de ces exceptions de niveau supérieur peut être personnalisée en affectant une autre fonction à trois arguments à sys.excepthook.
Exemple illustratif:
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