web-dev-qa-db-fra.com

Pourquoi ne puis-je pas gérer un KeyboardInterrupt en python?

J'écris du code Python 2.6.6 sur Windows qui ressemble à ceci:

try:
    dostuff()
except KeyboardInterrupt:
    print "Interrupted!"
except:
    print "Some other exception?"
finally:
    print "cleaning up...."
    print "done."

dostuff() est une fonction qui boucle en permanence, lisant une ligne à la fois à partir d’un flux d’entrée et agissant dessus. Je veux être capable de l'arrêter et de nettoyer quand je frappe Ctrl-C.

Ce qui se passe à la place, c'est que le code sous except KeyboardInterrupt: ne fonctionne pas du tout. La seule chose imprimée est "nettoyage ...", puis une trace est imprimée et ressemble à ceci:

Traceback (most recent call last):
  File "filename.py", line 119, in <module>
    print 'cleaning up...'
KeyboardInterrupt

Donc, le code de gestion des exceptions n'est PAS actif, et la trace en arrière prétend qu'un KeyboardInterrupt s'est produit pendant la clause finally, ce qui n'a pas de sens, car frapper ctrl-c est ce qui a provoqué l'exécution de cette partie! Même la clause except: générique n'est pas en cours d'exécution.

EDIT: Sur la base des commentaires, j'ai remplacé le contenu du bloc try: par sys.stdin.read (). Le problème se produit toujours exactement comme décrit, avec la première ligne du bloc finally: en cours d'exécution, puis en imprimant le même suivi.

EDIT # 2: Si j'ajoute à peu près n'importe quoi après la lecture, le gestionnaire fonctionne. Donc, cela échoue:

try:
    sys.stdin.read()
except KeyboardInterrupt:
    ...

Mais cela fonctionne:

try:
    sys.stdin.read()
    print "Done reading."
except KeyboardInterrupt:
    ...

Voici ce qui est imprimé:

Done reading. Interrupted!
cleaning up...
done.

Donc, pour une raison quelconque, la "lecture terminée". La ligne est imprimée, même si l’exception s’est produite sur la ligne précédente. Ce n'est pas vraiment un problème - je dois évidemment pouvoir gérer une exception n'importe où dans le bloc "try". Cependant, l’impression ne fonctionne pas normalement - elle n’imprime pas de nouvelle ligne après, comme c’est censé le faire! Le "interrompu" est imprimé sur la même ligne ... avec un espace devant, pour une raison quelconque ...? Quoi qu'il en soit, après cela, le code fait ce qu'il est censé faire.

Il me semble qu’il s’agit d’un bogue dans la gestion d’une interruption lors d’un appel système bloqué.

36
Josh

La gestion des exceptions asynchrones n’est malheureusement pas fiable (exceptions déclenchées par les gestionnaires de signaux, contextes extérieurs via C API, etc.). Vous pouvez augmenter vos chances de gérer correctement l'exception asynchrone s'il existe une certaine coordination dans le code pour déterminer quelle partie du code est responsable de sa capture (le plus haut possible dans la pile d'appels semble approprié, sauf pour des fonctions très critiques).

La fonction appelée (dostuff) ou les fonctions situées plus en aval de la pile peuvent elles-mêmes avoir un attrait pour KeyboardInterrupt ou BaseException que vous n'aviez pas/ne pouviez pas prendre en compte. 

Ce cas trivial fonctionnait parfaitement avec python 2.6.6 (x64) interactif + Windows 7 (64 bits):

>>> import time
>>> def foo():
...     try:
...             time.sleep(100)
...     except KeyboardInterrupt:
...             print "INTERRUPTED!"
...
>>> foo()
INTERRUPTED!  #after pressing ctrl+c

MODIFIER:

Après une enquête plus approfondie, j'ai essayé ce que je crois être l'exemple que d'autres ont utilisé pour reproduire le problème. J'étais paresseux alors j'ai oublié le "enfin"

>>> def foo():
...     try:
...             sys.stdin.read()
...     except KeyboardInterrupt:
...             print "BLAH"
...
>>> foo()

Cela revient immédiatement après avoir appuyé sur CTRL + C. La chose intéressante s’est produite lorsque j’ai immédiatement essayé de rappeler foo:

>>> foo()

Traceback (most recent call last):
  File "c:\Python26\lib\encodings\cp437.py", line 14, in decode
    def decode(self,input,errors='strict'):
KeyboardInterrupt

L'exception a été levée immédiatement sans que je frappe CTRL + C.

Cela semblerait logique - il semble que nous ayons affaire à des nuances dans la gestion des exceptions asynchrones en Python. Plusieurs instructions en bytecode peuvent être nécessaires avant que l’exception async ne soit réellement affichée puis levée dans le contexte d’exécution en cours. (C'est le comportement que j'ai vu en jouant avec dans le passé)

Voir l'API C: http://docs.python.org/c-api/init.html#PyThreadState_SetAsyncExc

Cela explique donc un peu pourquoi KeyboardInterrupt est soulevé dans le contexte de l'exécution de l'instruction finally dans cet exemple:

>>> def foo():
...     try:
...             sys.stdin.read()
...     except KeyboardInterrupt:
...             print "interrupt"
...     finally:
...             print "FINALLY"
...
>>> foo()
FINALLY
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 7, in foo
KeyboardInterrupt

Il pourrait y avoir un mélange insensé de gestionnaires de signaux personnalisés mélangés au gestionnaire standard KeyboardInterrupt/CTRL + C de l'interprète, ce qui entraîne ce type de comportement. Par exemple, l'appel read () voit le signal et se désiste, mais il re-soulève le signal après avoir désenregistré son gestionnaire. Je ne saurais pas à coup sûr sans inspecter le code base de l'interprète. 

C'est pourquoi je crains généralement de faire usage d'exceptions asynchrones ....

EDIT 2

Je pense qu'il y a de bonnes raisons pour un rapport de bogue.

Encore une fois plus de théories ... (juste basé sur le code lu) Voir le fichier source de l'objet: http://svn.python.org/view/python/branches/release26-maint/Objects/fileobject.c?revision=81277&view = balisage

file_read appelle Py_UniversalNewlineFread (). fread peut renvoyer une erreur avec errno = EINTR (il gère lui-même le signal). Dans ce cas, Py_UniversalNewlineFread () échoue mais n'effectue aucune vérification de signal avec PyErr_CheckSignals () afin que les gestionnaires puissent être appelés de manière synchrone. file_read efface l'erreur de fichier mais n'appelle pas non plus PyErr_CheckSignals (). 

Voir getline () et getline_via_fgets () pour des exemples d'utilisation. Le modèle est documenté dans ce rapport de bogue pour un problème similaire: ( http://bugs.python.org/issue1195 ). Il semble donc que le signal soit traité à une heure indéterminée par l’interprète. 

J'imagine qu'il est inutile de plonger plus profondément car il n'est pas encore clair si l'exemple sys.stdin.read () est un analogue approprié de votre fonction "dostuff ()". (il pourrait y avoir plusieurs bugs en jeu)

20
Jeremy Brown

Ayant le même problème et voici ma solution de contournement:

try:
    some_blocking_io_here() # CTRL-C to interrupt
except:
    try:
        print() # any i/o will get the second KeyboardInterrupt here?
    except:
        real_handler_here()
1
coqem2

sys.stdin.read() est un appel système et le comportement sera donc différent pour chaque système. Pour Windows 7, je pense que ce qui se passe, c’est que l’entrée est mise en mémoire tampon et que vous obtenez donc où sys.stdin.read() renvoie tout au Ctrl-C et dès que vous accédez à sys.stdin à nouveau, il envoie le "Ctrl- C ".

essayez ce qui suit,

def foo():
    try:
        print sys.stdin.read()
        print sys.stdin.closed
    except KeyboardInterrupt:
        print "Interrupted!"

Cela suggère qu'il y a une mise en mémoire tampon de stdin en cours qui provoque un autre appel à stdin pour reconnaître l'entrée au clavier

def foo():
    try:
        x=0
        while 1:
            x += 1
        print x
    except KeyboardInterrupt:
        print "Interrupted!"

il ne semble pas y avoir de problème.

Est-ce que dostuff() lit stdin?

1
milkypostman
def foo():
    try:
        x=0
        while 1:
            x+=1
            print (x)
    except KeyboardInterrupt:
       print ("interrupted!!")
foo()

Cela fonctionne bien.

0
Heinz Doepkemeier

Voici une hypothèse sur ce qui se passe:

  • appuyer sur Ctrl-C interrompt l'instruction "print" (pour une raison quelconque ... bug? limitation Win32?)
  • appuyer sur Ctrl-C lance également le premier KeyboardInterrupt, dans dostuff ()
  • Le gestionnaire d'exceptions s'exécute et tente d'imprimer "Interrupted", mais l'instruction "print" est rompue et génère un autre KeyboardInterrupt.
  • La clause finally s'exécute et essaie d'imprimer "clean up ....", mais l'instruction "print" est cassée et jette un autre KeyboardInterrupt.
0
user9876

Cela fonctionne pour moi:

import sys

if __== "__main__":
    try:
        sys.stdin.read()
        print "Here"
    except KeyboardInterrupt:
        print "Worked"
    except:
        print "Something else"
    finally:
        print "Finally"

Essayez de placer une ligne en dehors de votre fonction dostuff () ou de déplacer la condition de boucle en dehors de la fonction. Par exemple:

try:
    while True:
        dostuff()
except KeyboardInterrupt:
    print "Interrupted!"
except:
    print "Some other exception?"
finally:
    print "cleaning up...."
    print "done."
0
Jake