Bắt bàn phím Ngắt bằng Python trong khi tắt chương trình


81

Tôi đang viết một tiện ích dòng lệnh bằng Python, vì nó là mã sản xuất, nên có thể tắt sạch mà không đổ nhiều thứ (mã lỗi, dấu vết ngăn xếp, v.v.) ra màn hình. Điều này có nghĩa là tôi cần phải bắt các ngắt bàn phím.

Tôi đã thử sử dụng cả hai khối try catch như:

if __name__ == '__main__':
    try:
        main()
    except KeyboardInterrupt:
        print 'Interrupted'
        sys.exit(0)

và tự bắt tín hiệu (như trong bài đăng này ):

import signal
import sys

def sigint_handler(signal, frame):
    print 'Interrupted'
    sys.exit(0)
signal.signal(signal.SIGINT, sigint_handler)

Cả hai phương pháp dường như hoạt động khá tốt trong quá trình hoạt động bình thường. Tuy nhiên, nếu gián đoạn xảy ra trong khi mã dọn dẹp ở cuối ứng dụng, Python dường như luôn in thứ gì đó ra màn hình. Bắt được gián đoạn cho

^CInterrupted
Exception KeyboardInterrupt in <bound method MyClass.__del__ of <path.to.MyClass object at 0x802852b90>> ignored

trong khi xử lý tín hiệu cho

^CInterrupted
Exception SystemExit: 0 in <Finalize object, dead> ignored

hoặc là

^CInterrupted
Exception SystemExit: 0 in <bound method MyClass.__del__ of <path.to.MyClass object at 0x802854a90>> ignored

Những lỗi này không chỉ xấu mà còn không hữu ích lắm (đặc biệt đối với người dùng cuối không có mã nguồn)!

Mã dọn dẹp cho ứng dụng này khá lớn, vì vậy có nhiều khả năng vấn đề này sẽ do người dùng thực gây ra. Có cách nào để bắt hoặc chặn đầu ra này không, hay nó chỉ là thứ mà tôi sẽ phải giải quyết?


2
Tại sao bạn không thay thế sys.stdout/ sys.stderr? Như sys.stderr = open(os.devnull, 'w')thế nào?. Nếu bạn thực sự không quan tâm đến kết quả cuối cùng thì đây có vẻ như là giải pháp hiển nhiên.
Bakuriu

1
Có một os._exitnhưng nó trông giống như những con quỷ mũi đối với tôi. Mã dọn dẹp của bạn ở đâu, bạn có đang sử dụng mô-đun atexit cho việc đó không?
wim

1
@Bakuriu: Trong khi việc chuyển hướng stderr sẽ khiến đầu ra không hoạt động, nó cũng xử lý các lỗi hợp pháp mà người dùng có thể làm gì đó, như không tìm thấy tệp hoặc không thể truy cập máy chủ.
Dan

4
@Dan Nó không cần phải như vậy /dev/null. Bạn có thể viết một đối tượng giống tệp tùy chỉnh chỉ ẩn các thư có định dạng nhất định.
Bakuriu

2
@Bakuriu: Với tôi vẫn có vẻ khá khó hiểu. Tôi sẽ làm điều đó nếu tôi phải làm, nhưng tôi cảm thấy đây là thứ nên được tích hợp vào ngôn ngữ.
Dan

Câu trả lời:


115

Kiểm tra chủ đề này , nó có một số thông tin hữu ích về việc thoát và theo dõi.

Nếu bạn quan tâm hơn đến việc chỉ giết chương trình, hãy thử một cái gì đó như thế này (điều này cũng sẽ loại bỏ các chân từ dưới mã dọn dẹp):

if __name__ == '__main__':
    try:
        main()
    except KeyboardInterrupt:
        print('Interrupted')
        try:
            sys.exit(0)
        except SystemExit:
            os._exit(0)

6
Tôi khuyên bạn nên loại trừ as, sau đó lấy và sử dụng lại mã thoát.
wizzwizz 4

5
Đúng. Người ta chắc chắn không nên thoát với 0trên KeyboardInterrupt. Nếu bất kỳ ai đang sử dụng tập lệnh của bạn trong một tập lệnh, đường dẫn hoặc những gì họ sẽ nghĩ rằng mã của bạn được thực thi như bình thường.
user3342816

4
Linux thường thoát với 130: 128 + 2 tldp.org/LDP/abs/html/exitcodes.html#EXITCODESREF , Không thể tìm thấy bất kỳ chức năng mã thoát nền tảng nào trên python. Nhưng 1là dứt khoát tốt hơn sau đó0
user3342816

2
Nếu người dùng gọi chương trình python từ một tập lệnh bash và họ sử dụng set -etrong tập lệnh (nếu cần), bạn muốn ngắt toàn bộ tập lệnh bash sau khi người dùng nhấn CTRL + C. Điều này sẽ yêu cầu trả về một mã thoát khác 0.
tối đa

1
@Bersan: Cảm ơn bạn! Tôi đã đọc trên Grammy.com/blog/than-then , bây giờ tôi chỉ phải nhớ nó: D
user3342816

6

Bạn có thể bỏ qua các SIGINT sau khi quá trình tắt máy bắt đầu bằng cách gọi signal.signal(signal.SIGINT, signal.SIG_IGN)trước khi bạn bắt đầu mã dọn dẹp của mình.

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.