Захоплення клавіатури переривання в Python без спроб за винятком


102

Чи є якийсь спосіб у Python захопити KeyboardInterruptподію, не вкладаючи весь код всередину try- exceptоператора?

Я хочу чисто вийти без сліду, якщо користувач натискає Ctrl+ C.

Відповіді:


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)(спати протягом дня - це цілком довільна цифра).
Кріс Морган

1
Якщо ви використовуєте пропозицію Кріса Моргана використовувати 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

(Так, я знаю, що це не відповідає безпосередньо на запитання, але не зовсім зрозуміло, чому потрібна спроба / крім блоку є заперечною - можливо, це робить менше прикрою для ОП)


5
Чомусь це не завжди працює для мене. signal.signal( signal.SIGINT, lambda s, f : sys.exit(0))завжди робить.
Hal Canary

Це не завжди працює з такими речами, як pygtk, які використовують нитки. Іноді ^ C просто знищить поточний потік замість всього процесу, тому виняток поширюватиметься лише через цей потік.
Судо Баш

Там інша 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 Що ти маєш на увазі? У роботі з мультипроцесором вам доведеться мати справу з сигналом як у батьківському, так і дочірньому процесах, оскільки він може спрацьовувати в обох. Це дійсно залежить від того, що ви робите, і як буде використовуватися ваше програмне забезпечення.
Бакуріу

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цього вимагає модифікованої версії. Наразі я не можу уявити спосіб для функцій виходу-виклику знати про винятки; atexitобробник виловлює виняток, викликає зворотний зворотний дзвінок, а потім повторно підвищує це виняток. Але ви могли це зробити інакше.)

Для отримання додаткової інформації див:


atexit не працює для KeyboardInterrupt (python 3.7)
TimZaman

Тут працював KeyboardInterrupt (python 3.7, MacOS). Можливо, вигадка щодо певної платформи?
Ніко Німан

4

Ви можете запобігти друкуванню сліду стека KeyboardInterruptбез try: ... except KeyboardInterrupt: pass(найочевиднішого і найпростішого "найкращого" рішення, але ви вже знаєте це і попросили щось інше) замінивши 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. Ви вірні в тому, що винятки можна обробляти лише в спробі / за винятком випадків, але в цьому випадку ви можете утримувати виняток, коли він коли-небудь піднімався.
Метт

1
Так, я дізнався, що приблизно через три хвилини після публікації, коли відповідь котлінського задзвонила;)

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
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.