Comment faire taire "sys.excepthook est manquant erreur"?

NB: Je n'ai pas essayé de reproduire le problème décrit ci-dessous sous Windows, ou avec des versions de Python autres que 2.7.3.

la façon la plus fiable d'obtenir le problème en question Est de pipe la sortie du script de test suivant par : (bash):

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

I. e.:

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

Ma question est:

comment 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 piégée avec un try-except.)

l'exemple ci-dessus est, il est vrai, très artificiel, mais je rencontre le même problème parfois lorsque la sortie d'un de mes scripts est acheminée par un logiciel tiers.

Le message d'erreur est certainement inoffensif, mais il est déconcertant pour les utilisateurs finaux, donc je voudrais le silence il.

EDIT: le script suivant, qui diffère de l'original ci-dessus seulement 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
40
demandé sur kjo 2012-10-09 02:25:26

4 réponses

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 devez empêcher le script d'écrire quoi que ce soit à la sortie standard. Cela signifie supprimer tout print déclarations et toute utilisation de sys.stdout.write, ainsi que tout code qui appelle.

la raison pour laquelle cela se produit est que vous acheminez une quantité non nulle de sortie de votre script Python vers quelque chose qui ne se lit jamais à partir de l'entrée standard. Ce n'est pas unique à l' : commande; vous pouvez obtenir le même résultat en raccordant à n'importe quelle commande qui ne lit pas l'entrée standard, comme

python testscript.py | cd .

Ou pour un exemple simple, considérons un script printer.py contenant rien de plus que

print 'abcde'

python printer.py | python printer.py

produira la même erreur.

Lorsque vous rediriger la sortie d'un programme vers un autre, la sortie produite par l'écriture le programme est sauvegardé dans un buffer, et attend le programme de lecture pour demander que les données du buffer. Tant que le tampon n'est pas vide, toute tentative de fermer l'objet writing file est censée échouer avec une erreur. C'est la cause profonde des messages que vous voyez.

le code spécifique qui déclenche l'erreur est dans L'implémentation du langage C de Python, ce qui explique pourquoi vous ne pouvez pas l'attraper avec un try/except bloc: il court 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 parce qu'il y a encore une sortie tamponnée qui attend d'être lue. 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, donc cela échoue. Python essaie d'imprimer un message à sys.stderr, mais cela a déjà été déallocé donc encore une fois, il échoue. La raison pour laquelle vous voyez les messages à l'écran est que Le code Python contient une contingence fprintf pour écrire une sortie sur le pointeur de fichier directement, 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, regardons la séquence d'arrêt de L'interpréteur Python, qui est implémentée dans le Py_Finalize functionpythonrun.c.

  1. après avoir invoqué les crochets de sortie et coupé les fils, le finalisation du code des appels PyImport_Cleanup pour finaliser et désallouer tous les modules importés. La dernière tâche exécutée par cette fonction est supprimer sys module, qui consiste principalement à appeler _PyModule_Clear pour effacer toutes les entrées dans le dictionnaire du module-y compris, en particulier, les objets standard stream (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 compte de référence est décrémenté en utilisant Py_DECREF macro. Les objets dont le nombre de référence atteint zéro deviennent éligibles à la désallocation. Depuis l' sys module contient les dernières références aux objets standard stream, lorsque ces références sont désactivées par _PyModule_Clear, ils sont alors prêts à être libéré.1
  3. la désallocation D'un objet de fichier Python est accomplie par file_dealloc la fonctionfileobject.c. Cette première invoque l'objet du fichier Python close méthode en utilisant leclose_the_file function:

    ret = close_the_file(f);
    

    Pour un fichier standard de l'objet, close_the_file(f) délégués au C fclose function, qui définit une condition d'erreur s'il reste des données à écrire dans le pointeur de fichier. file_dealloc vérifie que la condition d'erreur et imprime le premier message que vous voir:

    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 tente alors d'afficher l'exception en utilisant PyErr_Print. Que les délégués à 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ée. 2 Python vérifie cette condition d'erreur et affiche le second message comme 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 des disparus excepthook, Python revient ensuite à imprimer les informations d'exception en utilisant PyErr_Display, qui est la méthode par défaut pour afficher une trace de pile. La première chose que cette fonction n'est d'essayer d'accès sys.stderr.

    PyObject *f = PySys_GetObject("stderr");
    

    Dans ce cas, cela ne fonctionne pas car sys.stderr a déjà été dégagé et inaccessible. 3 alors le code invoque fprintf directement pour envoyer le troisième message au flux d'erreurs standard C.

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

fait intéressant, le comportement est un peu différent en Python 3.4+ car la procédure de finalisation est maintenant élimine explicitement les flux de sortie et d'erreur standard avant que les modules ne soient effacés. De cette façon, si vous avez des données qui attendent d'être écrites, vous obtenez une erreur qui indique explicitement cette condition, plutôt qu'une défaillance" accidentelle " dans la procédure normale de finalisation. Aussi, si vous exécutez

python printer.py | python printer.py

utilisant Python 3.4 (après avoir mis les parenthèses sur le print déclaration bien sûr), vous n'avez aucune erreur. Je suppose que la deuxième invocation de Python peut consommer des entrées standard pour une raison ou une autre, mais c'est un tout autre problème.


1en Fait, c'est un mensonge. Le mécanisme d'importation de Python cache une copie du dictionnaire de chaque module importé, qui n'est libéré que _PyImport_Fini fonctionne, plus tard dans la mise en oeuvre de Py_Finalize et c'est lorsque les dernières références aux objets standard stream disparaissent. Une fois que le nombre de référence atteint zéro, Py_DECREF libère les objets . Mais tout ce qui importe pour la réponse principale est que les références sont retirées de la sys le dictionnaire du module, puis désallocé plus tard.

2Encore une fois, c'est parce que l' sys le dictionnaire du module est complètement effacé avant que tout soit vraiment désalloué, grâce au mécanisme de mise en cache des attributs. Vous pouvez lancer Python avec le -vv option pour voir tous les attributs du module être désactivé avant que vous obtenez le message d'erreur sur la fermeture du pointeur de fichier.

3ce comportement particulier est le seul partie qui n'a pas de sens à moins que vous ne connaissiez le mécanisme de mise en cache des attributs mentionné dans les notes précédentes.

61
répondu David Z 2016-03-31 12:15:45
--- 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

puis avec ce script rien ne se passe, car l'exception (IOError: [Errno 32] broken pipe) est supprimée par l'essai...sauf.

$ python testscript.py  | :
$
11
répondu Andrew 2014-07-25 17:05:40

dans votre programme lance une exception qui ne peut pas être attrapée en utilisant try/except block. Pour l'attraper, remplacer la fonction sys.excepthook:

import sys
sys.excepthook = lambda *args: None

documentation:

sys.exceptithook (type, valeur, traceback)

Lorsqu'une exception est soulevée et non saisie, l'interprète appelle sys.exceptihook avec trois arguments, la classe exception, exception exemple, et un objet traceback. Dans une session interactive ce se produit juste avant que le contrôle soit retourné à l'invite; dans un Python programme cela se produit juste avant la fin du programme. Le traitement de ces exceptions de haut niveau peuvent être personnalisées en assignant une autre fonction à trois arguments de sys.excepthook.

exemple:

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
répondu defuz 2012-10-08 22:38:48

je me rends compte que c'est une vieille question, mais je l'ai trouvé dans une recherche Google pour l'erreur. Dans mon cas, c'était une erreur de codage. L'une de mes dernières déclarations a été:

print "Good Bye"

la solution était simplement de fixer la syntaxe à:

print ("Good Bye")

[Raspberry Pi Zéro, Python 2.7.9]

-4
répondu Stephen Mann 2017-03-18 02:29:13