Try-hariç Python'da keyboardinterrupt'ı yakalayın


102

Python'da KeyboardInterrupttüm kodu bir try- exceptifadesinin içine koymadan olayı yakalamanın bir yolu var mı ?

Kullanıcı Ctrl+ tuşlarına basarsa iz bırakmadan temiz bir şekilde çıkmak istiyorum C.

Yanıtlar:


150

Evet, modül kullanarak bir kesme işleyicisi yükleyebilirsiniz sinyali ve kullanma sonsuza kadar bekler 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()

10
Sinyal modülüyle ilgili bazı platforma özgü sorunlar olduğunu unutmayın - bu posteri etkilememelidir, ancak "Windows'ta, signal () yalnızca SIGABRT, SIGFPE, SIGILL, SIGINT, SIGSEGV veya SIGTERM ile çağrılabilir. Bir Değer Hatası başka bir durumda yükseltilecektir. "
bgporter

7
İpliklerle de iyi çalışır. Umarım asla while True: continueyapmazsın. (Bu tarzda while True: passzaten daha düzgün olurdu.) Bu çok israf olur; gibi bir şey deneyin while True: time.sleep(60 * 60 * 24)(her seferinde bir gün uyumak tamamen keyfi bir rakamdır).
Chris Morgan

1
Chris Morgan'ın time(gerektiği gibi) önerisini kullanıyorsanız , şunu unutmayın import time:)
Seaux,

1
Sys.exit (0) 'ı çağırmak benim için bir SystemExit istisnasını tetikliyor.
Şununla

2
Tekrar tekrar uyumak yerine signal.pause () kullanabilirsiniz
Croad Langshan

36

Tek istediğiniz geri dönüşü göstermemekse, kodunuzu şu şekilde yapın:

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


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

(Evet, bunun soruyu doğrudan cevaplamadığını biliyorum, ancak denemeye / hariç bloğa ihtiyaç duymanın neden sakıncalı olduğu gerçekten net değil - belki bu, OP için daha az sinir bozucu hale getiriyor)


5
Bazı nedenlerden dolayı, bu her zaman benim için çalışmıyor. signal.signal( signal.SIGINT, lambda s, f : sys.exit(0))her zaman yapar.
Hal Kanarya

Bu her zaman iplik kullanan pygtk gibi şeylerde çalışmaz. Bazen ^ C, tüm süreç yerine sadece geçerli iş parçacığını öldürecektir, bu nedenle istisna yalnızca bu iş parçacığı boyunca yayılır.
Sudo Bash


30

Kendi sinyal işleyicinizi ayarlamanın bir alternatifi, istisnayı yakalamak ve yok saymak için bir bağlam yöneticisi kullanmaktır:

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

Bu, try- exceptbloğunu kaldırırken, neler olup bittiğine dair bazı açık sözler korunur.

Bu ayrıca, her seferinde sinyal işleyicileri yeniden ayarlamanıza ve sıfırlamanıza gerek kalmadan, yalnızca kodunuzun bazı bölümlerinde kesmeyi yok saymanıza olanak tanır.


1
güzel, bu çözüm sinyallerle uğraşmaktan çok amacı ifade etmede biraz daha doğrudan görünüyor.
Seaux

Çoklu işlem kitaplığını kullanarak, bu yöntemleri hangi nesneye eklemem gerektiğinden emin değilim .. herhangi bir ipucu?
Stéphane

@ Stéphane Ne demek istiyorsun? Çoklu işlemle uğraşırken, her ikisinde de tetiklenebileceğinden, sinyalle hem üst hem de alt süreçlerde ilgilenmeniz gerekecektir. Gerçekten ne yaptığınıza ve yazılımınızın nasıl kullanılacağına bağlıdır.
Bakuriu

8

Bunun eski bir soru olduğunu biliyorum ama önce buraya geldim ve sonra atexitmodülü keşfettim . Platformlar arası geçmiş geçmişi veya uyarıların tam listesi hakkında henüz bir bilgim yok, ancak şu ana kadar KeyboardInterruptLinux'ta temizlik sonrası işlemlerle başa çıkmaya çalışırken tam olarak aradığım şey buydu . Soruna başka bir şekilde yaklaşmak istedim.

Fabric işlemleri bağlamında çıkış sonrası temizlik yapmak istiyorum, bu yüzden her şeyi sarmalamak benim için de bir seçenek değildi try/ exceptdeğildi. Gibi hissediyorum atexitkodunuzu kontrol akışının üst düzeyinde değil Böyle bir durumda, iyi bir uyum olabilir.

atexit çok yetenekli ve kutudan çıktığı gibi okunabilir, örneğin:

import atexit

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

atexit.register(goodbye)

Bunu bir dekoratör olarak da kullanabilirsiniz (2.6'dan itibaren; bu örnek dokümanlardan alınmıştır):

import atexit

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

KeyboardInterruptSadece özel yapmak istiyorsanız , bu soruya başka birinin cevabı muhtemelen daha iyidir.

Ancak atexitmodülün yalnızca ~ 70 satırlık kod olduğunu ve istisnaları farklı şekilde ele alan benzer bir sürüm oluşturmanın zor olmayacağını unutmayın, örneğin istisnaları argüman olarak geri çağırma işlevlerine iletmek. (Bunun sınırlandırılması atexitdeğiştirilmiş bir sürümü garanti eder: şu anda çıkış-geri arama işlevlerinin istisnaları bilmesinin bir yolunu düşünemiyorum; atexitişleyici istisnayı yakalar, geri aramalarınızı çağırır ve sonra yeniden yükseltir bu istisna. Ancak bunu farklı şekilde yapabilirsiniz.)

Daha fazla bilgi için bkz .:


atexit, KeyboardInterrupt (python 3.7) için çalışmıyor
TimZaman

KeyboardInterrupt için burada çalıştı (python 3.7, MacOS). Belki platforma özgü bir tuhaflık?
Niko Nyman

Teyit edebilir atexitpiton 3.7 ve 3.8 için hem MacOS ve Ubuntu 18.04 için çalışmalarını
Ainz titor

4

Sen bir yığın izini baskı önleyebilir KeyboardInterruptolmadan try: ... except KeyboardInterrupt: pass(Propably en belirgin ve "en iyi" çözümü, ama zaten bildiğimiz ve başka bir şey istedi) değiştirerek sys.excepthook. Gibi bir şey

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

Kullanıcı ctrl-c tuşlarına basarsa iz bırakmadan temiz bir çıkış istiyorum
Alex

7
Bu hiç de doğru değil. KeyboardInterrupt istisnası, bir kesme işleyicisi sırasında oluşturulur. SIGINT için varsayılan işleyici KeyboardInterrupt'ı yükseltir, bu yüzden eğer bu davranışı istemezseniz tek yapmanız gereken SIGINT için farklı bir sinyal işleyici sağlamaktır. İstisnaların yalnızca denemede ele alınabileceği konusunda haklısınız, ancak bu durumda istisnanın ilk etapta ortaya çıkmasını önleyebilirsiniz.
Matt

1
Evet, bunu gönderdikten yaklaşık üç dakika sonra, kotlinski'nin cevabı geldiğinde öğrendim;)

2

Herkes tarafından önerilen çözümleri denedim, ancak gerçekten çalışmasını sağlamak için kodu kendim doğaçlama yapmak zorunda kaldım. Doğaçlama kodum aşağıdadır:

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

Hızlı bir minimal çözüm arayan biri varsa,

import signal

# The code which crashes program on interruption

signal.signal(signal.SIGINT, call_this_function_if_interrupted)

# The code skipped if interrupted
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.