Почему я не могу обработать KeyboardInterrupt в Python? - PullRequest
37 голосов
/ 05 января 2011

Я пишу код Python 2.6.6 для окон, который выглядит следующим образом:

try:
    dostuff()
except KeyboardInterrupt:
    print "Interrupted!"
except:
    print "Some other exception?"
finally:
    print "cleaning up...."
    print "done."

dostuff() - это функция, которая зацикливается навсегда, читая строку за раз из входного потока и действуяв теме.Я хочу иметь возможность остановить его и очистить, когда я нажму ctrl-c.

Вместо этого происходит то, что код под except KeyboardInterrupt: вообще не работает.Единственное, что выводится на печать - это «очистка ...», а затем выводится обратная запись, которая выглядит следующим образом:

Traceback (most recent call last):
  File "filename.py", line 119, in <module>
    print 'cleaning up...'
KeyboardInterrupt

Итак, код обработки исключений НЕ выполняется, и отслеживание утверждает, чтоKeyboardInterrupt произошел во время предложения finally , что не имеет смысла, потому что нажатие ctrl-c - это то, что заставило эту часть работать в первую очередь!Даже общее предложение except: не выполняется.

EDIT: На основании комментариев я заменил содержимое блока try: на sys.stdin.read ().Проблема по-прежнему возникает точно так же, как описано, с первой строкой блока finally:, работающей и затем печатающей ту же трассировку.

РЕДАКТИРОВАТЬ # 2: Если после добавления чего-нибудь добавитьчитай, обработчик работает.Итак, это терпит неудачу:

try:
    sys.stdin.read()
except KeyboardInterrupt:
    ...

Но это работает:

try:
    sys.stdin.read()
    print "Done reading."
except KeyboardInterrupt:
    ...

Вот что напечатано:

Done reading. Interrupted!
cleaning up...
done.

Итак, по какой-то причине "Закончено чтение«.строка печатается, хотя исключение произошло в предыдущей строке.Это на самом деле не проблема - очевидно, я должен быть в состоянии обработать исключение в любом месте блока try.Тем не менее, печать не работает нормально - она ​​не печатает новую строку впоследствии, как это должно быть!"Прерванный" напечатан в той же строке ... с пробелом перед ним, по некоторым причинам ...?В любом случае, после этого код делает то, что должен.

Мне кажется, что это ошибка в обработке прерывания во время заблокированного системного вызова.

Ответы [ 6 ]

21 голосов
/ 05 января 2011

Асинхронная обработка исключений, к сожалению, ненадежна (исключения, вызываемые обработчиками сигналов, внешние контексты через C API и т. Д.). Вы можете увеличить свои шансы на правильную обработку асинхронного исключения, если в коде есть некоторая координация в отношении того, какой фрагмент кода отвечает за их перехват (максимальный возможный в стеке вызовов представляется подходящим, за исключением очень важных функций).

Вызываемая функция (dostuff) или функции, расположенные ниже по стеку, могут сами по себе поймать KeyboardInterrupt или BaseException, которые вы не учли / не смогли учесть.

Этот тривиальный случай прекрасно работал с Python 2.6.6 (x64) Interactive + Windows 7 (64bit):

>>> import time
>>> def foo():
...     try:
...             time.sleep(100)
...     except KeyboardInterrupt:
...             print "INTERRUPTED!"
...
>>> foo()
INTERRUPTED!  #after pressing ctrl+c

EDIT:

После дальнейшего расследования я попробовал то, что, по моему мнению, является примером, который другие использовали для воспроизведения проблемы. Мне было лень, поэтому я пропустил «наконец»

>>> def foo():
...     try:
...             sys.stdin.read()
...     except KeyboardInterrupt:
...             print "BLAH"
...
>>> foo()

Возвращается сразу после нажатия CTRL + C. Интересная вещь произошла, когда я сразу попытался снова вызвать foo:

>>> foo()

Traceback (most recent call last):
  File "c:\Python26\lib\encodings\cp437.py", line 14, in decode
    def decode(self,input,errors='strict'):
KeyboardInterrupt

Исключение возникло сразу, и я не нажал CTRL + C.

Казалось бы, это имеет смысл - похоже, мы имеем дело с нюансами в том, как обрабатываются асинхронные исключения в Python. Это может занять несколько инструкций байт-кода, прежде чем асинхронное исключение будет фактически выведено, а затем вызвано в текущем контексте выполнения. (Такое поведение я видел, когда играл с ним в прошлом)

См. C API: http://docs.python.org/c-api/init.html#PyThreadState_SetAsyncExc

Таким образом, это несколько объясняет, почему KeyboardInterrupt возникает в контексте выполнения оператора finally в этом примере:

>>> def foo():
...     try:
...             sys.stdin.read()
...     except KeyboardInterrupt:
...             print "interrupt"
...     finally:
...             print "FINALLY"
...
>>> foo()
FINALLY
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 7, in foo
KeyboardInterrupt

Там может быть какое-то безумное смешение пользовательских обработчиков сигналов, смешанных со стандартным обработчиком KeyboardInterrupt / CTRL + C интерпретатора, что приводит к такому поведению. Например, вызов read () видит сигнал и освобождает под залог, но он повторно поднимает сигнал после отмены регистрации его обработчика. Я бы не знал наверняка без проверки кодовой базы интерпретатора.

Вот почему я обычно уклоняюсь от использования асинхронных исключений ....

РЕДАКТИРОВАТЬ 2

Я думаю, что есть хороший повод для сообщения об ошибке.

Опять больше теорий ... (только на основе чтения кода). См. Источник объекта файла: http://svn.python.org/view/python/branches/release26-maint/Objects/fileobject.c?revision=81277&view=markup

file_read вызывает Py_UniversalNewlineFread (). fread может вернуться с ошибкой с errno = EINTR (он выполняет свою собственную обработку сигналов). В этом случае Py_UniversalNewlineFread () выдает ошибку, но не выполняет никакой проверки сигнала с помощью PyErr_CheckSignals (), чтобы обработчики могли вызываться синхронно. file_read очищает ошибку файла, но также не вызывает PyErr_CheckSignals ().

Смотрите примеры использования getline () и getline_via_fgets (). Шаблон описан в этом отчете об ошибке для аналогичной проблемы: (http://bugs.python.org/issue1195). Таким образом, кажется, что сигнал обрабатывается переводчиком в неопределенное время.

Полагаю, что погружение глубже не имеет смысла, поскольку до сих пор не ясно, является ли пример sys.stdin.read () правильным аналогом вашей функции "dostuff ()". (в игре может быть несколько ошибок)

1 голос
/ 15 июля 2014

Возникла похожая проблема, и это мой обходной путь:

try:
    some_blocking_io_here() # CTRL-C to interrupt
except:
    try:
        print() # any i/o will get the second KeyboardInterrupt here?
    except:
        real_handler_here()
1 голос
/ 05 января 2011

sys.stdin.read() - системный вызов, поэтому поведение будет различным для каждой системы. Для windows 7 я думаю, что происходит то, что ввод буферизуется, и вы получаете, где sys.stdin.read() возвращает все до Ctrl-C, и как только вы снова получите доступ к sys.stdin, он отправит Ctrl-C».

попробуйте следующее,

def foo():
    try:
        print sys.stdin.read()
        print sys.stdin.closed
    except KeyboardInterrupt:
        print "Interrupted!"

Это говорит о том, что происходит буферизация stdin, которая заставляет другой вызов stdin распознать ввод с клавиатуры

def foo():
    try:
        x=0
        while 1:
            x += 1
        print x
    except KeyboardInterrupt:
        print "Interrupted!"

Похоже, проблем нет.

dostuff() читает со стандартного ввода?

0 голосов
/ 29 августа 2015
def foo():
    try:
        x=0
        while 1:
            x+=1
            print (x)
    except KeyboardInterrupt:
       print ("interrupted!!")
foo()

Это прекрасно работает.

0 голосов
/ 05 января 2011

Это работает для меня:

import sys

if __name__ == "__main__":
    try:
        sys.stdin.read()
        print "Here"
    except KeyboardInterrupt:
        print "Worked"
    except:
        print "Something else"
    finally:
        print "Finally"

Попробуйте поставить строку за пределами функции dostuff () или переместите условие цикла за пределы функции. Например:

try:
    while True:
        dostuff()
except KeyboardInterrupt:
    print "Interrupted!"
except:
    print "Some other exception?"
finally:
    print "cleaning up...."
    print "done."
0 голосов
/ 05 января 2011

Вот предположение о том, что происходит:

  • нажатие Ctrl-C нарушает оператор "print" (по какой-то причине ... ошибка? Ограничение Win32?)
  • нажатие Ctrl-C также вызывает первый KeyboardInterrupt в dostuff ()
  • Обработчик исключений запускается и пытается вывести «Interrupted», но оператор «print» не работает и выдает еще один KeyboardInterrupt.
  • Предложение finally выполняется и пытается вывести «cleanup ....», но оператор «print» не работает и выдает еще одно KeyboardInterrupt.
...