Tangkap interupsi keyboard dengan Python tanpa coba-kecuali

101

Apakah ada cara Python untuk menangkap KeyboardInterruptacara tanpa memasukkan semua kode di dalam try-except pernyataan ?

Saya ingin keluar dengan bersih tanpa jejak jika pengguna menekan Ctrl+ C.

Alex
sumber

Jawaban:

149

Ya, Anda dapat menginstal sebuah interrupt handler menggunakan modul sinyal , dan menunggu selamanya menggunakan threading.Event :

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
sumber
10
Perhatikan bahwa ada beberapa masalah khusus platform dengan modul sinyal - seharusnya tidak memengaruhi poster ini, tetapi "Di Windows, signal () hanya dapat dipanggil dengan SIGABRT, SIGFPE, SIGILL, SIGINT, SIGSEGV, atau SIGTERM. A ValueError akan dibesarkan dalam kasus lain. "
bgporter
7
Bekerja dengan baik dengan utas juga. Saya harap Anda tidak pernah melakukannya while True: continue. (Dengan gaya itu, while True: passakan lebih rapi.) Itu akan sangat boros; cobalah sesuatu seperti while True: time.sleep(60 * 60 * 24)(tidur selama sehari pada suatu waktu adalah gambaran yang sepenuhnya sewenang-wenang).
Chris Morgan
1
Jika Anda menggunakan saran Chris Morgan untuk menggunakan time(sebagaimana mestinya), jangan lupa import time:)
Seaux
1
Memanggil sys.exit (0) memicu pengecualian SystemExit untuk saya. Anda dapat membuatnya berfungsi dengan baik jika Anda menggunakannya dalam kombinasi dengan ini: stackoverflow.com/a/13723190/353094
leetNightshade
2
Anda dapat menggunakan signal.pause () daripada tidur berulang kali
Croad Langshan
36

Jika yang Anda inginkan hanyalah tidak menampilkan traceback, buat kode Anda seperti ini:

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


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

(Ya, saya tahu bahwa ini tidak secara langsung menjawab pertanyaan, tetapi tidak terlalu jelas mengapa membutuhkan blok coba / kecuali tidak menyenangkan - mungkin ini membuatnya kurang mengganggu bagi OP)

bgporter
sumber
5
Untuk beberapa alasan, ini tidak selalu berhasil untuk saya. signal.signal( signal.SIGINT, lambda s, f : sys.exit(0))selalu begitu.
Hal Canary
Ini tidak selalu berfungsi dengan hal-hal seperti pygtk yang menggunakan utas. Terkadang ^ C hanya akan mematikan utas saat ini, bukan seluruh proses, jadi pengecualian hanya akan menyebar melalui utas itu.
Sudo Bash
Ada pertanyaan SO lain secara khusus tentang Ctrl + C dengan pygtk: stackoverflow.com/questions/16410852/…
bgporter
30

Alternatif untuk menyetel penangan sinyal Anda sendiri adalah dengan menggunakan pengelola konteks untuk menangkap pengecualian dan mengabaikannya:

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

Ini menghapus blok try- exceptsambil tetap mempertahankan beberapa penyebutan eksplisit tentang apa yang sedang terjadi.

Ini juga memungkinkan Anda untuk mengabaikan interupsi hanya di beberapa bagian kode Anda tanpa harus mengatur dan mengatur ulang penangan sinyal lagi setiap saat.

Bakuriu
sumber
1
Bagus, solusi ini tampaknya sedikit lebih langsung dalam mengungkapkan tujuan daripada menangani sinyal.
Seaux
Menggunakan pustaka multiprosesing, saya tidak yakin objek mana yang harus saya tambahkan metode tersebut .. ada petunjuk?
Stéphane
@ Stéphane Apa maksud Anda? Saat berurusan dengan multiprosesing, Anda harus berurusan dengan sinyal di kedua proses induk dan anak, karena mungkin dipicu di keduanya. Ini sangat tergantung pada apa yang Anda lakukan dan bagaimana perangkat lunak Anda akan digunakan.
Bakuriu
8

Saya tahu ini adalah pertanyaan lama tetapi saya datang ke sini lebih dulu dan kemudian menemukan atexitmodulnya. Saya belum tahu tentang rekam jejak lintas platformnya atau daftar lengkap peringatannya, tetapi sejauh ini persis seperti yang saya cari dalam mencoba menangani pasca KeyboardInterruptpembersihan di Linux. Hanya ingin membuang cara lain untuk mendekati masalah.

Saya ingin melakukan pembersihan pasca keluar dalam konteks operasi Fabric, jadi membungkus semuanya try / exceptjuga bukan pilihan bagi saya. Saya merasa atexitmungkin cocok untuk situasi seperti itu, di mana kode Anda tidak berada di tingkat atas aliran kontrol.

atexit sangat mampu dan mudah dibaca, misalnya:

import atexit

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

atexit.register(goodbye)

Anda juga dapat menggunakannya sebagai dekorator (mulai 2.6; contoh ini dari dokumen):

import atexit

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

Jika Anda ingin membuatnya spesifik KeyboardInterrupt, jawaban orang lain untuk pertanyaan ini mungkin lebih baik.

Tapi perhatikan bahwa file atexit modul ini hanya terdiri dari ~ 70 baris kode dan tidak akan sulit untuk membuat versi serupa yang memperlakukan pengecualian secara berbeda, misalnya meneruskan pengecualian sebagai argumen ke fungsi callback. (Batasan atexititu akan menjamin versi yang dimodifikasi: saat ini saya tidak dapat membayangkan cara agar fungsi exit-callback mengetahui tentang pengecualian; atexitpenangan menangkap pengecualian, memanggil callback Anda, lalu memunculkan kembali pengecualian itu. Tetapi Anda dapat melakukan ini secara berbeda.)

Untuk info lebih lanjut lihat:

driftcatcher
sumber
atexit tidak berfungsi untuk KeyboardInterrupt (python 3.7)
TimZaman
Bekerja untuk KeyboardInterrupt di sini (python 3.7, MacOS). Mungkin permainan unik khusus platform?
Niko Nyman
4

Anda dapat mencegah pencetakan jejak tumpukan untuk KeyboardInterrupt, tanpa try: ... except KeyboardInterrupt: pass(solusi "terbaik" yang paling jelas dan mungkin, tetapi Anda sudah mengetahuinya dan meminta sesuatu yang lain) dengan mengganti sys.excepthook. Sesuatu seperti

def custom_excepthook(type, value, traceback):
    if type is KeyboardInterrupt:
        return # do nothing
    else:
        sys.__excepthook__(type, value, traceback)
Richard
sumber
Saya ingin keluar bersih tanpa jejak jika pengguna menekan ctrl-c
Alex
7
Ini tidak benar sama sekali. Pengecualian KeyboardInterrupt dibuat selama penangan interupsi. Penangan default untuk SIGINT memunculkan KeyboardInterrupt jadi jika Anda tidak menginginkan perilaku itu, yang harus Anda lakukan adalah menyediakan penangan sinyal yang berbeda untuk SIGINT. Anda benar karena pengecualian hanya dapat ditangani dalam percobaan / kecuali namun dalam kasus ini Anda dapat mencegah pengecualian tersebut dimunculkan sejak awal.
Matt
1
Ya, saya mengetahui itu sekitar tiga menit setelah memposting, ketika jawaban kotlinski masuk;)
2

Saya mencoba solusi yang disarankan oleh semua orang, tetapi saya harus mengimprovisasi kode sendiri untuk benar-benar membuatnya berfungsi. Berikut adalah kode improvisasi saya:

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

Jika seseorang mencari solusi minimal yang cepat,

import signal

# The code which crashes program on interruption

signal.signal(signal.SIGINT, call_this_function_if_interrupted)

# The code skipped if interrupted
tejasvi88.dll
sumber