จับคีย์บอร์ดขัดจังหวะใน Python โดยไม่ต้องพยายามยกเว้น


102

มีวิธีใดบ้างใน Python ในการจับภาพKeyboardInterruptเหตุการณ์โดยไม่ต้องใส่รหัสทั้งหมดในคำสั่งtry-except

ฉันต้องการที่จะออกจากหมดจดไร้ร่องรอยถ้าผู้ใช้กด+CtrlC

คำตอบ:


150

ได้คุณสามารถติดตั้งตัวจัดการขัดจังหวะโดยใช้สัญญาณโมดูลและรอตลอดไปโดยใช้เธรดเหตุการณ์:

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
โปรดทราบว่ามีปัญหาเฉพาะบางแพลตฟอร์มกับโมดูลสัญญาณ - ไม่ควรส่งผลกระทบต่อโปสเตอร์นี้ แต่ "ใน Windows สัญญาณ () สามารถเรียกได้ด้วย SIGABRT, SIGFPE, SIGILL, SIGINT, SIGSEGV หรือ SIGTERM เท่านั้น ValueError จะถูกยกขึ้นในกรณีอื่น ๆ "
bgporter

7
ทำงานได้ดีกับเธรดด้วย ฉันหวังว่าคุณไม่เคยทำwhile True: continueแต่ (ในรูปแบบนั้นwhile True: passก็น่าจะดูดีกว่าอยู่ดี) นั่นจะเป็นการสิ้นเปลืองมาก ลองทำสิ่งต่างๆเช่นwhile True: time.sleep(60 * 60 * 24)(การนอนวันละครั้งเป็นเรื่องที่คิดขึ้นเองทั้งหมด)
Chris Morgan

1
หากคุณกำลังใช้คำแนะนำของ Chris Morgan ในการใช้time(เท่าที่ควร) อย่าลืมimport time:)
Seaux

1
การเรียก sys.exit (0) ทำให้เกิดข้อยกเว้น SystemExit สำหรับฉัน คุณสามารถทำให้มันใช้งานได้ดีหากใช้ร่วมกับสิ่งนี้: stackoverflow.com/a/13723190/353094
leetNightshade

2
คุณสามารถใช้ signal.pause () แทนการนอนซ้ำได้
Croad Langshan

36

หากสิ่งที่คุณต้องการคือไม่แสดงการย้อนกลับให้สร้างรหัสของคุณดังนี้:

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


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

(ใช่ฉันรู้ว่าสิ่งนี้ไม่ได้ตอบคำถามโดยตรง แต่ก็ไม่ชัดเจนว่าทำไมต้องลอง / ยกเว้นบล็อกจึงไม่เหมาะสม - อาจทำให้ OP น่ารำคาญน้อยลง)


5
ด้วยเหตุผลบางประการสิ่งนี้ไม่ได้ผลสำหรับฉันเสมอไป signal.signal( signal.SIGINT, lambda s, f : sys.exit(0))ทำเสมอ
Hal Canary

สิ่งนี้ใช้ไม่ได้กับสิ่งต่างๆเช่น pygtk ที่ใช้เธรด บางครั้ง ^ C จะฆ่าเธรดปัจจุบันแทนที่จะเป็นกระบวนการทั้งหมดดังนั้นข้อยกเว้นจะแพร่กระจายผ่านเธรดนั้นเท่านั้น
Sudo Bash

มีคำถาม SO อื่นโดยเฉพาะเกี่ยวกับ Ctrl + C กับ pygtk: stackoverflow.com/questions/16410852/…
bgporter

30

อีกทางเลือกหนึ่งในการตั้งค่าตัวจัดการสัญญาณของคุณเองคือการใช้ตัวจัดการบริบทเพื่อตรวจจับข้อยกเว้นและเพิกเฉย:

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

การดำเนินการนี้จะลบtry- exceptบล็อกในขณะที่รักษาการพูดถึงสิ่งที่เกิดขึ้นอย่างชัดเจน

นอกจากนี้ยังช่วยให้คุณสามารถละเว้นการขัดจังหวะในบางส่วนของรหัสของคุณโดยไม่ต้องตั้งค่าและรีเซ็ตตัวจัดการสัญญาณทุกครั้ง


1
ดีวิธีนี้ดูเหมือนจะตรงกว่าเล็กน้อยในการแสดงวัตถุประสงค์แทนที่จะจัดการกับสัญญาณ
Seaux

การใช้ไลบรารีการประมวลผลหลายขั้นตอนฉันไม่แน่ใจว่าฉันควรเพิ่มวิธีการเหล่านั้นวัตถุใด .. มีเงื่อนงำใด
Stéphane

@ Stéphaneคุณหมายถึงอะไร? เมื่อจัดการกับการประมวลผลหลายขั้นตอนคุณจะต้องจัดการกับสัญญาณทั้งในกระบวนการแม่และลูกเนื่องจากสัญญาณอาจถูกทริกเกอร์ทั้งสองอย่าง ขึ้นอยู่กับสิ่งที่คุณกำลังทำและวิธีการใช้ซอฟต์แวร์ของคุณ
Bakuriu

8

ฉันรู้ว่านี่เป็นคำถามเก่า แต่ฉันมาที่นี่ก่อนแล้วจึงค้นพบatexitโมดูล ฉันยังไม่รู้เกี่ยวกับบันทึกการติดตามข้ามแพลตฟอร์มหรือรายการคำเตือนทั้งหมด แต่จนถึงตอนนี้มันเป็นสิ่งที่ฉันกำลังมองหาในการพยายามจัดการหลังการKeyboardInterruptล้างข้อมูลบน Linux เพียงแค่อยากจะโยนไปในทางอื่นในการเข้าถึงปัญหา

ฉันต้องการล้างข้อมูลหลังออกในบริบทของการดำเนินการ Fabric ดังนั้นการรวมทุกอย่างไว้ในtry/ exceptไม่ใช่ตัวเลือกสำหรับฉันเช่นกัน ฉันรู้สึกว่าatexitอาจเหมาะสมกับสถานการณ์ดังกล่าวโดยที่โค้ดของคุณไม่ได้อยู่ในขั้นตอนการควบคุมระดับบนสุด

atexit มีความสามารถและอ่านได้นอกกรอบเช่น:

import atexit

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

atexit.register(goodbye)

คุณยังสามารถใช้เป็นมัณฑนากร (ณ 2.6 ตัวอย่างนี้มาจากเอกสาร):

import atexit

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

หากคุณต้องการกำหนดให้เฉพาะเจาะจงKeyboardInterruptเท่านั้นคำตอบของบุคคลอื่นสำหรับคำถามนี้น่าจะดีกว่า

แต่โปรดทราบว่าatexitโมดูลมีโค้ดเพียงประมาณ 70 บรรทัดและการสร้างเวอร์ชันที่คล้ายกันซึ่งถือว่าข้อยกเว้นต่างกันนั้นไม่ยากเช่นการส่งข้อยกเว้นเป็นอาร์กิวเมนต์ไปยังฟังก์ชันเรียกกลับ (ข้อ จำกัด ของatexitสิ่งนี้จะรับประกันเวอร์ชันที่แก้ไข: ขณะนี้ฉันไม่สามารถคิดวิธีที่ฟังก์ชัน exit-callback-ทราบเกี่ยวกับข้อยกเว้นได้atexitตัวจัดการจะจับข้อยกเว้นเรียกการโทรกลับของคุณจากนั้นเพิ่มขึ้นอีกครั้ง ข้อยกเว้นนั้น แต่คุณสามารถทำได้แตกต่างออกไป)

ดูข้อมูลเพิ่มเติมได้ที่:


atexit ไม่ทำงานสำหรับ KeyboardInterrupt (python 3.7)
TimZaman

ทำงานสำหรับ KeyboardInterrupt ที่นี่ (python 3.7, MacOS) อาจจะเป็นมุมแหลมเฉพาะแพลตฟอร์ม?
Niko Nyman

4

คุณสามารถป้องกันการพิมพ์กองติดตามสำหรับKeyboardInterruptโดยไม่ต้องtry: ... except KeyboardInterrupt: pass(ที่เห็นได้ชัดที่สุดและ propably "ดีที่สุด" วิธีการแก้ปัญหา แต่คุณรู้อยู่แล้วว่ามันและถามหาสิ่งอื่น) sys.excepthookโดยการแทนที่ สิ่งที่ต้องการ

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

ฉันต้องการทางออกที่สะอาดโดยไม่มีร่องรอยหากผู้ใช้กด ctrl-c
Alex

7
นี่ไม่เป็นความจริงเลย ข้อยกเว้น KeyboardInterrupt ถูกสร้างขึ้นระหว่างตัวจัดการขัดจังหวะ ตัวจัดการเริ่มต้นสำหรับ SIGINT จะเพิ่ม KeyboardInterrupt ดังนั้นหากคุณไม่ต้องการให้เกิดพฤติกรรมดังกล่าวสิ่งที่คุณต้องทำคือจัดหาตัวจัดการสัญญาณอื่นสำหรับ SIGINT ของคุณถูกต้องในข้อยกเว้นนั้นสามารถจัดการได้ในการทดลองเท่านั้น / ยกเว้นอย่างไรก็ตามในกรณีนี้คุณสามารถป้องกันไม่ให้มีการยกข้อยกเว้นขึ้นมาตั้งแต่แรก
Matt

1
ใช่ฉันเรียนรู้ว่าประมาณสามนาทีหลังจากโพสต์เมื่อคำตอบของ kotlinski เข้ามา;)

2

ฉันลองใช้วิธีแก้ปัญหาที่แนะนำโดยทุกคน แต่ฉันต้องโพล่งโค้ดด้วยตัวเองเพื่อให้มันใช้งานได้จริง ต่อไปนี้เป็นรหัสชั่วคราวของฉัน:

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

หากมีใครบางคนกำลังค้นหาวิธีแก้ปัญหาขั้นต่ำอย่างรวดเร็ว

import signal

# The code which crashes program on interruption

signal.signal(signal.SIGINT, call_this_function_if_interrupted)

# The code skipped if interrupted
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.