2011-01-05 7 views
32

Sto scrivendo codice Python 2.6.6 su Windows che assomiglia a questo:Perché non riesco a gestire un KeyboardInterrupt in python?

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

dostuff() è una funzione che loop per sempre, la lettura di una linea alla volta da un flusso di input e di agire su di esso. Voglio essere in grado di fermarlo e ripulire quando premo ctrl-c.

Quello che succede invece è che il codice sotto except KeyboardInterrupt: non funziona affatto. L'unica cosa che viene stampato è "pulendo ...", e poi un traceback viene stampato che assomiglia a questo:

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

Quindi, il codice di gestione delle eccezioni non è in esecuzione, e il traceback sostiene che un KeyboardInterrupt si è verificato durante la clausola finale, che non ha senso perché battere ctrl-c è ciò che ha causato quella parte da eseguire in primo luogo! Anche la clausola generica except: non è in esecuzione.

MODIFICA: In base ai commenti, ho sostituito il contenuto del blocco try: con sys.stdin.read(). Il problema si verifica ancora esattamente come descritto, con la prima riga del blocco finally: in esecuzione e quindi la stampa dello stesso traceback.

MODIFICA # 2: Se aggiungo praticamente qualsiasi cosa dopo la lettura, il gestore funziona. Quindi, questo non funziona:

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

Ma questo funziona:

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

Ecco cosa c'è stampato: "finito di leggere"

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

Così, per qualche motivo, la la linea viene stampata, anche se l'eccezione si è verificata sulla riga precedente. Questo non è davvero un problema - ovviamente devo essere in grado di gestire un'eccezione ovunque all'interno del blocco "prova". Tuttavia, la stampa non funziona normalmente - non stampa successivamente una nuova riga come dovrebbe! Il "Interruped" è stampato sulla stessa linea ... con uno spazio prima, per qualche motivo ...? Comunque, dopo che il codice fa quello che dovrebbe.

Mi sembra che questo sia un bug nella gestione di un interrupt durante una chiamata di sistema bloccata.

+5

Mostra il codice per il tuo dostuff(), perché questo codice dovrebbe funzionare (e lo fa) – user225312

+0

Funziona come previsto con Python 2.5.1. – khachik

+4

riprodotto con Python 2.7, sostituendo 'dostuff()' con 'sys.stdin.read()' – balpha

risposta

0

Ecco un'ipotesi su ciò che sta succedendo: (?? Per qualsiasi motivo ... bug limitazione Win32)

  • premendo Ctrl-C rompe l'istruzione "stampa"
  • premendo Ctrl-C getta anche la first KeyboardInterrupt, in dostuff()
  • Il gestore di eccezioni esegue e prova a stampare "Interrotto", ma l'istruzione "stampa" è interrotta e genera un altro KeyboardInterrupt.
  • La clausola finally viene eseguita e tenta di stampare "clean up ....", ma l'istruzione "print" è interrotta e genera ancora un altro KeyboardInterrupt.
+0

Sei riuscito a riprodurre il problema? Windows presumo? – user225312

+1

Non c'è modo che un'eccezione possa "interrompere" 'print' (o qualsiasi altra funzione per quella materia). Anche questo otterrebbe un n-times-stacked con "durante la gestione dell'eccezione sopra, un'altra eccezione si è verificata" in mezzo. – delnan

18

La gestione asincrona delle eccezioni non è purtroppo affidabile (eccezioni generate dai gestori di segnale, dai contesti esterni tramite API C, ecc.).È possibile aumentare le possibilità di gestire correttamente l'eccezione asincrona se vi è una certa coordinazione nel codice su quale pezzo di codice è responsabile per la cattura (il più alto possibile nello stack di chiamate sembra appropriato tranne che per funzioni molto critiche).

La funzione chiamata (dostuff) o le funzioni più in basso nello stack possono di per sé avere un catch per KeyboardInterrupt o BaseException a cui non si è/non si è potuto tenere conto.

questo caso banale funzionato bene con Python 2.6.6 (x64) interattivo + Windows 7 (64 bit):

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

EDIT:

Con ulteriori indagini, ho provato quello che credo è l'esempio che altri hanno usato per riprodurre il problema. Ero pigro quindi ho omesso "finalmente"

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

Questo ritorna immediatamente dopo aver premuto CTRL + C. La cosa interessante è accaduto quando ho provato subito a chiamare foo ancora:

>>> 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'eccezione è stata sollevata immediatamente senza colpire me CTRL + C.

Questo sembra avere un senso: sembra che si tratti di sfumature nel modo in cui le eccezioni asincrone vengono gestite in Python. Può richiedere diverse istruzioni bytecode prima che l'eccezione asincrona venga effettivamente visualizzata e quindi sollevata all'interno del contesto di esecuzione corrente. (Questo è il comportamento che ho visto quando si gioca con essa in passato)

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

Quindi questo in qualche modo spiega perché KeyboardInterrupt viene sollevata nell'ambito dell'esecuzione del finally in questo esempio:

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

ci potrebbe essere qualche mescolanza folle di gestori di segnali personalizzati mescolato con gestore KeyboardInterrupt/CTRL + C standard dell'interprete che sta causando questo tipo di comportamento. Ad esempio, la chiamata read() vede il segnale e bails, ma rilancia il segnale dopo aver annullato la registrazione del suo gestore. Non saprei di sicuro senza ispezionare il codice di base dell'interprete.

Questo è il motivo per cui di solito rifuggire dal fare uso di eccezioni asincrone ....

EDIT 2

Penso che ci sia un buon argomento per una segnalazione di bug.

Ancora una volta di più le teorie ... (solo sulla base di codice di lettura) Vedere la fonte oggetto file: http://svn.python.org/view/python/branches/release26-maint/Objects/fileobject.c?revision=81277&view=markup

file_read chiama Py_UniversalNewlineFread(). fread può tornare con un errore con errno = EINTR (esegue la propria gestione dei segnali). In questo caso Py_UniversalNewlineFread() esegue l'handailing ma non esegue alcun controllo del segnale con PyErr_CheckSignals() in modo che i gestori possano essere richiamati in modo sincrono. file_read cancella l'errore del file ma non chiama anche PyErr_CheckSignals().

Vedere getline() e getline_via_fgets() per esempi di come viene utilizzato. Il modello è documentato in questa segnalazione di bug per un problema simile: (http://bugs.python.org/issue1195).Quindi sembra che il segnale sia gestito a tempo indeterminato dall'interprete.

Immagino che ci sia poco valore nell'immergersi in profondità poiché non è ancora chiaro se l'esempio sys.stdin.read() sia un analogo corretto della funzione "dostuff()". (Ci potrebbero essere molteplici bug in gioco)

+0

Ma altri (incluso me - Windows 7 32 bit con Python3 qui) lo hanno riprodotto con le sostituzioni 'dostuff' che non catturano KeyboardInterrupt. – delnan

+0

@delnan - risposta espansa. spero che questo aiuti a formulare alcune teorie! :) –

+4

Non definirei questa "sfumatura" in eccezioni asincrone tanto quanto un vero bug. Se l'eccezione viene sollevata un po 'più tardi rispetto a quando si verifica tecnicamente, va bene ... ma se l'eccezione viene sollevata FUORI dal blocco try che l'eccezione ha fatto scoppiare, questa è un'altra storia. Diversi comportamenti ben documentati sono esplicitamente infranti qui, soprattutto perché un blocco "finally" non esegue il codice clean-up "garantito"; e anche che nessun gestore di eccezioni viene eseguito quando almeno un'eccezione si verifica senza ambiguità all'interno di un blocco "try". – Josh

0

Questo funziona per me:

import sys 

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

Prova a mettere una linea esterna della funzione() DoStuff o spostare la condizione del ciclo di fuori della funzione. Ad esempio:

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

sys.stdin.read() è una chiamata di sistema e quindi il comportamento sarà diverso per ciascun sistema. Per Windows 7 penso che quello che sta succedendo è che l'input è in fase di buffering e quindi stai ottenendo dove sys.stdin.read() restituisce tutto fino a Ctrl-C e non appena accedi a sys.stdin di nuovo invierà il "Ctrl- C".

prova i seguenti,

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

Ciò suggerisce che v'è un buffer di stdin succede che causa un'altra chiamata stdin riconoscere l'input della tastiera

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

non sembra essere un problema

dostuff() sta leggendo da stdin?

+0

Penso che tu sia su qualcosa, tranne che se si sostituisce sys.stdin.closed con qualsiasi altro comando, incluso un print o sleep, anche questo fa sì che funzioni. Non credo che sia necessario accedere a stdin una seconda volta ... Penso che forse premendo ctrl-c la chiamata di sistema ritorni e poi Invia anche un interrupt al software, ma python non gestisce l'interrupt fino a dopo prossima riga di codice ... per allora è già fuori dal blocco try! – Josh

+0

Giusto, ma non sono sicuro di quello che stai facendo nel tuo 'dostuff()' che non lo farebbe anche accadere. – milkypostman

1

Avendo problema simile e questa è la mia soluzione:

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() 
0
def foo(): 
    try: 
     x=0 
     while 1: 
      x+=1 
      print (x) 
    except KeyboardInterrupt: 
     print ("interrupted!!") 
foo() 

che funziona bene.