Capture a interrupção do teclado em Python sem tentar, exceto

101

Existe alguma maneira no Python de capturar KeyboardInterrupteventos sem colocar todo o código dentro de uma instrução try- except?

Desejo sair sem deixar rastros se o usuário pressionar Ctrl+ C.

Alex
fonte

Respostas:

149

Sim, você pode instalar um manipulador de interrupção usando o sinal do módulo e esperar para sempre usando um threading . Evento :

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()
Johan Kotlinski
fonte
10
Observe que existem alguns problemas específicos da plataforma com o módulo de sinal - não deve afetar este cartaz, mas "No Windows, signal () só pode ser chamado com SIGABRT, SIGFPE, SIGILL, SIGINT, SIGSEGV ou SIGTERM. Um ValueError será gerado em qualquer outro caso. "
bgporter
7
Funciona bem com threads também. Espero que você nunca faça isso while True: continue. (Nesse estilo, while True: passseria mais puro, de qualquer maneira.) Isso seria um grande desperdício; tente algo como while True: time.sleep(60 * 60 * 24)(dormir um dia de cada vez é uma figura inteiramente arbitrária).
Chris Morgan,
1
Se você estiver usando a sugestão de Chris Morgan de usar time(como deveria), não se esqueça de import time:)
Seaux
1
Chamar sys.exit (0) dispara uma exceção SystemExit para mim. Você pode fazer funcionar bem se usá-lo em combinação com isto: stackoverflow.com/a/13723190/353094
leetNightshade
2
Você pode usar signal.pause () em vez de dormir repetidamente
Croad Langshan
36

Se tudo o que você quer é não mostrar o traceback, faça seu código assim:

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


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

(Sim, eu sei que isso não responde diretamente à pergunta, mas não está muito claro por que a necessidade de um bloco try / except é questionável - talvez isso o torne menos incômodo para o OP)

bgporter
fonte
5
Por algum motivo, isso nem sempre funciona para mim. signal.signal( signal.SIGINT, lambda s, f : sys.exit(0))sempre faz.
Hal Canary
Isso nem sempre funciona com coisas como pygtk que usam threads. Às vezes, ^ C apenas eliminará a thread atual em vez de todo o processo, de modo que a exceção só se propagará por meio dessa thread.
Sudo Bash
Há outra pergunta do SO especificamente sobre Ctrl + C com pygtk: stackoverflow.com/questions/16410852/…
bgporter
30

Uma alternativa para definir seu próprio manipulador de sinal é usar um gerenciador de contexto para capturar a exceção e ignorá-la:

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

Isso remove o bloco try- exceptenquanto preserva alguma menção explícita do que está acontecendo.

Isso também permite que você ignore a interrupção apenas em algumas partes do seu código, sem ter que definir e redefinir novamente os manipuladores de sinal todas as vezes.

Bakuriu
fonte
1
bom, essa solução parece um pouco mais direta ao expressar o propósito em vez de lidar com sinais.
Seaux
Usando a biblioteca de multiprocessamento, não tenho certeza em qual objeto devo adicionar esses métodos ... alguma pista?
Stéphane
@ Stéphane O que você quer dizer? Ao lidar com multiprocessamento, você terá que lidar com o sinal nos processos pai e filho, uma vez que pode ser disparado em ambos. Realmente depende do que você está fazendo e de como o software será usado.
Bakuriu
8

Eu sei que esta é uma pergunta antiga, mas eu vim aqui primeiro e depois descobri o atexitmódulo. Não sei sobre seu histórico de plataforma cruzada ou uma lista completa de advertências ainda, mas até agora é exatamente o que eu estava procurando ao tentar lidar com o pós- KeyboardInterruptlimpeza no Linux. Só queria apresentar outra maneira de abordar o problema.

Quero fazer uma limpeza pós-saída no contexto das operações do Fabric, portanto, encerrar tudo try/ exceptnão era uma opção para mim também. Acho que atexitpode ser um bom ajuste em tal situação, onde seu código não está no nível superior do fluxo de controle.

atexit é muito capaz e legível fora da caixa, por exemplo:

import atexit

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

atexit.register(goodbye)

Você também pode usá-lo como um decorador (a partir de 2.6; este exemplo é dos documentos):

import atexit

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

Se você quiser torná-lo específico KeyboardInterruptapenas para , a resposta de outra pessoa a essa pergunta provavelmente é melhor.

Mas observe que o atexitmódulo tem apenas ~ 70 linhas de código e não seria difícil criar uma versão semelhante que trate as exceções de maneira diferente, por exemplo, passando as exceções como argumentos para as funções de retorno de chamada. (A limitação atexitdisso garantiria uma versão modificada: atualmente, não consigo conceber uma maneira de as funções de retorno de chamada de saída saberem sobre as exceções; o atexitmanipulador captura a exceção, chama seu (s) retorno (ões) de chamada e aumenta novamente essa exceção. Mas você poderia fazer isso de forma diferente.)

Para obter mais informações, consulte:

apanhador
fonte
atexit não funciona para KeyboardInterrupt (python 3.7)
TimZaman
Trabalhou para KeyboardInterrupt aqui (python 3.7, MacOS). Talvez uma peculiaridade específica da plataforma?
Niko Nyman
4

Você pode evitar a impressão de um rastreamento de pilha para KeyboardInterrupt, sem try: ... except KeyboardInterrupt: pass(a solução mais óbvia e provavelmente a "melhor", mas você já a conhece e pediu outra coisa) substituindo sys.excepthook. Algo como

def custom_excepthook(type, value, traceback):
    if type is KeyboardInterrupt:
        return # do nothing
    else:
        sys.__excepthook__(type, value, traceback)
Richard
fonte
Quero uma saída limpa sem rastros se o usuário pressionar ctrl-c
Alex
7
Isto não é verdade, de forma alguma. A exceção KeyboardInterrupt é criada durante um manipulador de interrupção. O manipulador padrão para SIGINT gera o KeyboardInterrupt, portanto, se você não quiser esse comportamento, tudo o que você precisa fazer é fornecer um manipulador de sinal diferente para SIGINT. Você está correto em que as exceções só podem ser tratadas em uma tentativa / exceto, no entanto, neste caso, você pode evitar que a exceção seja gerada em primeiro lugar.
Matt
1
Sim, aprendi isso cerca de três minutos após a postagem, quando a resposta de kotlinski apareceu;)
2

Tentei as soluções sugeridas por todos, mas tive que improvisar o código sozinho para fazê-lo funcionar. A seguir está meu código improvisado:

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
Rohit Jain
fonte
0

Se alguém está em busca de uma solução rápida e mínima,

import signal

# The code which crashes program on interruption

signal.signal(signal.SIGINT, call_this_function_if_interrupted)

# The code skipped if interrupted
tejasvi88
fonte