web-dev-qa-db-fra.com

Comment réduire l'erreur "sys.excepthook est manquant"?

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
44
kjo

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.

Détails techniques

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.

  1. Après avoir appelé les hooks de sortie et fermé les threads, le code de finalisation appelle 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.
  2. Lorsqu'une valeur est supprimée d'un dictionnaire ou remplacée par une nouvelle valeur, son nombre de références est décrémenté en utilisant la macro 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.1
  3. La 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);
    }
    
  4. 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);
    }
    
  5. 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.

66
David Z

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  | :
$
11
Andrew

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
2
defuz