Cattura interruzioni da tastiera in Python senza tentativi eccetto


101

C'è un modo in Python per catturare un KeyboardInterruptevento senza mettere tutto il codice all'interno di un'istruzione try- except?

Voglio uscire in modo pulito senza lasciare traccia se l'utente preme Ctrl+ C.

Risposte:


149

Sì, puoi installare un gestore di interrupt utilizzando il segnale del modulo e attendere per sempre utilizzando un threading .

import signal
import sys
import time
import threading

def signal_handler(signal, frame):
    print('You pressed Ctrl+C!')
    sys.exit(0)

signal.signal(signal.SIGINT, signal_handler)
print('Press Ctrl+C')
forever = threading.Event()
forever.wait()

10
Tieni presente che ci sono alcuni problemi specifici della piattaforma con il modulo signal - non dovrebbero interessare questo poster, ma "Su Windows, signal () può essere chiamato solo con SIGABRT, SIGFPE, SIGILL, SIGINT, SIGSEGV o SIGTERM. A ValueError sarà sollevato in qualsiasi altro caso. "
bgporter

7
Funziona bene anche con i thread. Spero che tu non lo faccia mai while True: continue, però. (In quello stile, while True: passsarebbe più ordinato, comunque.) Sarebbe molto dispendioso; prova qualcosa di simile while True: time.sleep(60 * 60 * 24)(dormire per un giorno alla volta è una cifra del tutto arbitraria).
Chris Morgan

1
Se stai usando il suggerimento di Chris Morgan di usare time(come dovresti), non dimenticare di import time:)
Seaux

1
La chiamata di sys.exit (0) innesca un'eccezione SystemExit per me. È possibile farlo funzionare bene se lo si utilizza in combinazione con questo: stackoverflow.com/a/13723190/353094
leetNightshade

2
Puoi usare signal.pause () invece di dormire ripetutamente
Croad Langshan

36

Se tutto ciò che vuoi è non mostrare il traceback, crea il tuo codice in questo modo:

## all your app logic here
def main():
   ## whatever your app does.


if __name__ == "__main__":
   try:
      main()
   except KeyboardInterrupt:
      # do nothing here
      pass

(Sì, so che questo non risponde direttamente alla domanda, ma non è molto chiaro perché sia ​​discutibile la necessità di un blocco try / tranne - forse questo lo rende meno fastidioso per l'OP)


5
Per qualche ragione, questo non funziona sempre per me. signal.signal( signal.SIGINT, lambda s, f : sys.exit(0))lo fa sempre.
Hal Canary

Questo non funziona sempre con cose come pygtk che usano i thread. A volte ^ C ucciderà semplicemente il thread corrente invece dell'intero processo, quindi l'eccezione si propagherà solo attraverso quel thread.
Sudo Bash

C'è un'altra domanda SO specificamente Ctrl + C con pygtk: stackoverflow.com/questions/16410852/...
bgporter

30

Un'alternativa all'impostazione del proprio gestore di segnali è utilizzare un gestore di contesto per catturare l'eccezione e ignorarla:

>>> class CleanExit(object):
...     def __enter__(self):
...             return self
...     def __exit__(self, exc_type, exc_value, exc_tb):
...             if exc_type is KeyboardInterrupt:
...                     return True
...             return exc_type is None
... 
>>> with CleanExit():
...     input()    #just to test it
... 
>>>

Questo rimuove il blocco try- exceptpreservando una menzione esplicita di ciò che sta accadendo.

Ciò consente anche di ignorare l'interrupt solo in alcune parti del codice senza dover impostare e reimpostare nuovamente i gestori di segnale ogni volta.


1
bello, questa soluzione sembra un po 'più diretta nell'esprimere lo scopo piuttosto che trattare i segnali.
Seaux

Utilizzando la libreria multiprocessing, non sono sicuro su quale oggetto dovrei aggiungere questi metodi .. qualche indizio?
Stéphane

@ Stéphane Cosa intendi? Quando si ha a che fare con il multiprocessing, si dovrà gestire il segnale sia nel processo genitore che in quello figlio, poiché potrebbe essere attivato in entrambi. Dipende davvero da cosa stai facendo e da come verrà utilizzato il tuo software.
Bakuriu

8

So che questa è una vecchia domanda, ma prima sono venuto qui e poi ho scoperto il atexitmodulo. Non so ancora del suo track record multipiattaforma o di un elenco completo di avvertenze, ma finora è esattamente quello che stavo cercando nel tentativo di gestire la post- KeyboardInterruptpulizia su Linux. Volevo solo lanciare un altro modo di affrontare il problema.

Voglio eseguire la pulizia post-uscita nel contesto delle operazioni di Fabric, quindi anche il wrapping di tutto try/ exceptnon era un'opzione per me. Penso che atexitpossa essere una buona soluzione in una situazione del genere, in cui il tuo codice non è al livello più alto del flusso di controllo.

atexit è molto capace e leggibile fuori dagli schemi, ad esempio:

import atexit

def goodbye():
    print "You are now leaving the Python sector."

atexit.register(goodbye)

Puoi anche usarlo come decoratore (a partire dalla 2.6; questo esempio è tratto dalla documentazione):

import atexit

@atexit.register
def goodbye():
    print "You are now leaving the Python sector."

Se vuoi renderlo specifico KeyboardInterruptsolo per, la risposta di un'altra persona a questa domanda è probabilmente migliore.

Ma si noti che il atexitmodulo è solo ~ 70 righe di codice e non sarebbe difficile creare una versione simile che tratti le eccezioni in modo diverso, ad esempio passando le eccezioni come argomenti alle funzioni di callback. (La limitazione di atexitciò giustificherebbe una versione modificata: attualmente non riesco a concepire un modo per le funzioni di callback di uscita di conoscere le eccezioni; il atexitgestore cattura l'eccezione, chiama i tuoi callback, quindi solleva nuovamente quell'eccezione. Ma potresti farlo in modo diverso.)

Per maggiori informazioni vedere:


atexit non funziona per KeyboardInterrupt (python 3.7)
TimZaman

Ha lavorato per KeyboardInterrupt qui (python 3.7, MacOS). Forse un capriccio specifico della piattaforma?
Niko Nyman

4

Puoi impedire la stampa di una traccia dello stack KeyboardInterruptsenza try: ... except KeyboardInterrupt: pass(la soluzione più ovvia e probabilmente "migliore", ma la conosci già e hai chiesto qualcos'altro) sostituendo sys.excepthook. Qualcosa di simile a

def custom_excepthook(type, value, traceback):
    if type is KeyboardInterrupt:
        return # do nothing
    else:
        sys.__excepthook__(type, value, traceback)

Voglio un'uscita pulita senza traccia se l'utente preme ctrl-c
Alex

7
Questo non è affatto vero. L'eccezione KeyboardInterrupt viene creata durante un gestore di interrupt. Il gestore predefinito per SIGINT solleva il KeyboardInterrupt quindi se non si desidera questo comportamento tutto ciò che si dovrebbe fare è fornire un diverso gestore del segnale per SIGINT. Hai ragione sul fatto che le eccezioni possono essere gestite solo in un tentativo / tranne tuttavia in questo caso puoi impedire che l'eccezione venga sollevata in primo luogo.
Matt

1
Sì, l'ho saputo circa tre minuti dopo la pubblicazione, quando è arrivata la risposta di kotlinski;)

2

Ho provato le soluzioni suggerite da tutti, ma ho dovuto improvvisare del codice da solo per farlo funzionare. Di seguito è il mio codice improvvisato:

import signal
import sys
import time

def signal_handler(signal, frame):
    print('You pressed Ctrl+C!')
    print(signal) # Value is 2 for CTRL + C
    print(frame) # Where your execution of program is at moment - the Line Number
    sys.exit(0)

#Assign Handler Function
signal.signal(signal.SIGINT, signal_handler)

# Simple Time Loop of 5 Seconds
secondsCount = 5
print('Press Ctrl+C in next '+str(secondsCount))
timeLoopRun = True 
while timeLoopRun:  
    time.sleep(1)
    if secondsCount < 1:
        timeLoopRun = False
    print('Closing in '+ str(secondsCount)+ ' seconds')
    secondsCount = secondsCount - 1

0

Se qualcuno è alla ricerca di una rapida soluzione minima,

import signal

# The code which crashes program on interruption

signal.signal(signal.SIGINT, call_this_function_if_interrupted)

# The code skipped if interrupted
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.