Chụp ngắt bàn phím bằng Python mà không cần thử ngoại trừ


101

Có cách nào trong Python để nắm bắt KeyboardInterruptsự kiện mà không cần đặt tất cả mã bên trong câu lệnh try- exceptkhông?

Tôi muốn thoát sạch mà không có dấu vết nếu người dùng nhấn Ctrl+ C.

Câu trả lời:


149

Có, bạn có thể cài đặt trình xử lý ngắt bằng tín hiệu mô-đun và đợi mãi mãi bằng cách sử dụng luồng .

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
Lưu ý rằng có một số vấn đề cụ thể của nền tảng với mô-đun tín hiệu - sẽ không ảnh hưởng đến áp phích này, nhưng "Trên Windows, signal () chỉ có thể được gọi bằng SIGABRT, SIGFPE, SIGILL, SIGINT, SIGSEGV hoặc SIGTERM. A ValueError sẽ được nêu ra trong bất kỳ trường hợp nào khác. "
bgporter

7
Hoạt động tốt với các chủ đề, quá. Tôi hy vọng bạn không bao giờ làm while True: continuevậy. (Theo phong cách while True: passđó, dù sao cũng sẽ gọn gàng hơn.) Điều đó sẽ rất lãng phí; thử một cái gì đó như while True: time.sleep(60 * 60 * 24)(ngủ một ngày trong một thời gian là một con số hoàn toàn tùy ý).
Chris Morgan

1
Nếu bạn đang sử dụng gợi ý của Chris Morgan về việc sử dụng time(khi bạn nên làm), đừng quên import time:)
Seaux

1
Gọi sys.exit (0) kích hoạt ngoại lệ SystemExit cho tôi. Bạn có thể làm cho nó hoạt độc đáo nếu bạn sử dụng nó kết hợp với điều này: stackoverflow.com/a/13723190/353094
leetNightshade

2
Bạn có thể sử dụng signal.pause () thay vì ngủ nhiều lần
Croad Langshan

36

Nếu tất cả những gì bạn muốn là không hiển thị dấu vết, hãy tạo mã của bạn như sau:

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


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

(Vâng, tôi biết rằng điều này không trực tiếp trả lời câu hỏi, nhưng không thực sự rõ ràng tại sao cần thử / ngoại trừ khối là điều khó chịu - có thể điều này làm cho nó ít khó chịu hơn đối với OP)


5
Vì một số lý do, điều này không phải lúc nào cũng hiệu quả với tôi. signal.signal( signal.SIGINT, lambda s, f : sys.exit(0))luôn luôn.
Hal Canary

Điều này không phải lúc nào cũng hoạt động với những thứ như pygtk sử dụng các luồng. Đôi khi ^ C sẽ chỉ giết luồng hiện tại thay vì toàn bộ quá trình, vì vậy ngoại lệ sẽ chỉ truyền qua luồng đó.
Sudo Bash

Có một câu hỏi SO khác cụ thể về Ctrl + C với pygtk: stackoverflow.com/questions/16410852/…
bgporter.

30

Một cách thay thế để đặt trình xử lý tín hiệu của riêng bạn là sử dụng trình quản lý ngữ cảnh để bắt ngoại lệ và bỏ qua nó:

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

Điều này loại bỏ try-except block trong khi vẫn giữ một số đề cập rõ ràng về những gì đang xảy ra.

Điều này cũng cho phép bạn bỏ qua sự gián đoạn chỉ trong một số phần mã của bạn mà không cần phải thiết lập và đặt lại lần nữa các bộ xử lý tín hiệu.


1
tốt, giải pháp này có vẻ trực tiếp hơn một chút trong việc thể hiện mục đích hơn là xử lý các tín hiệu.
Seaux

Sử dụng thư viện đa xử lý, tôi không chắc mình nên thêm các phương thức đó vào đối tượng nào .. bất kỳ manh mối nào?
Stéphane

@ Stéphane Ý bạn là gì? Khi xử lý đa xử lý, bạn sẽ phải xử lý tín hiệu trong cả quy trình mẹ và con, vì nó có thể được kích hoạt ở cả hai. Nó thực sự phụ thuộc vào những gì bạn đang làm và cách phần mềm của bạn sẽ được sử dụng.
Bakuriu

8

Tôi biết đây là một câu hỏi cũ nhưng tôi đã đến đây trước và sau đó khám phá ra atexit mô-đun. Tôi chưa biết về hồ sơ theo dõi đa nền tảng của nó hoặc danh sách đầy đủ các cảnh báo, nhưng cho đến nay, đó chính xác là những gì tôi đang tìm kiếm khi cố gắng xử lý sau KeyboardInterruptdọn dẹp trên Linux. Chỉ muốn ném vào một cách khác để tiếp cận vấn đề.

Tôi muốn thực hiện dọn dẹp sau khi thoát trong bối cảnh hoạt động của Vải, vì vậy gói mọi thứ trong try/ exceptkhông phải là một lựa chọn cho tôi. Tôi cảm thấy nhưatexit có thể phù hợp trong tình huống như vậy, khi mã của bạn không ở cấp cao nhất của luồng kiểm soát.

atexit rất có khả năng và có thể đọc được ngay từ hộp, ví dụ:

import atexit

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

atexit.register(goodbye)

Bạn cũng có thể sử dụng nó như một trình trang trí (kể từ 2.6; ví dụ này là từ tài liệu):

import atexit

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

Nếu bạn chỉ muốn làm cho nó cụ thể KeyboardInterrupt, câu trả lời của người khác cho câu hỏi này có lẽ tốt hơn.

Nhưng lưu ý rằng atexitmô-đun chỉ có ~ 70 dòng mã và sẽ không khó để tạo một phiên bản tương tự xử lý các ngoại lệ khác nhau, ví dụ: chuyển các ngoại lệ làm đối số cho các hàm gọi lại. (Hạn chế củaatexit điều đó sẽ đảm bảo một phiên bản được sửa đổi: hiện tại tôi không thể hình dung được cách để các hàm exit-callback biết về các ngoại lệ; atexittrình xử lý bắt ngoại lệ, gọi (các) gọi lại của bạn, sau đó tăng lại ngoại lệ đó. Nhưng bạn có thể làm điều này theo cách khác.)

Để biết thêm thông tin, hãy xem:


atexit doesnt' làm việc cho KeyboardInterrupt (python 3.7)
TimZaman

Làm việc cho KeyboardInterrupt tại đây (python 3.7, MacOS). Có thể là một câu chuyện kỳ ​​quặc dành riêng cho nền tảng?
Niko Nyman

4

Bạn có thể ngăn việc in dấu vết ngăn xếp KeyboardInterruptmà không cần try: ... except KeyboardInterrupt: pass(giải pháp rõ ràng nhất và có thể đề xuất "tốt nhất", nhưng bạn đã biết điều đó và yêu cầu một thứ khác) bằng cách thay thế sys.excepthook. Cái gì đó như

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

Tôi muốn thoát ra mà không sạch dấu vết nếu người dùng nhấn ctrl-c
Alex

7
Điều này là không đúng sự thật cả. Ngoại lệ KeyboardInterrupt được tạo trong một trình xử lý ngắt. Trình xử lý mặc định cho SIGINT tăng KeyboardInterrupt, vì vậy nếu bạn không muốn hành vi đó, tất cả những gì bạn phải làm là cung cấp một trình xử lý tín hiệu khác cho SIGINT. Bạn đúng ở chỗ các ngoại lệ chỉ có thể được xử lý trong một lần thử / ngoại trừ tuy nhiên trong trường hợp này, bạn có thể giữ cho ngoại lệ không được nêu ra ngay từ đầu.
Matt

1
Vâng, tôi biết được điều đó khoảng ba phút sau khi đăng, khi câu trả lời của kotlinski đến;)

2

Tôi đã thử các giải pháp được đề xuất bởi mọi người, nhưng tôi phải tự ứng biến mã để thực sự hoạt động. Sau đây là mã ngẫu hứng của tôi:

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

Nếu ai đó đang tìm kiếm một giải pháp tối thiểu nhanh chóng,

import signal

# The code which crashes program on interruption

signal.signal(signal.SIGINT, call_this_function_if_interrupted)

# The code skipped if interrupted
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.