2011-01-05 3 views
32

Я пишу Python 2.6.6 код на окнах выглядит следующим образом:Почему я не могу обрабатывать KeyboardInterrupt в python?

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: и последующей печатью той же трассировки.

EDIT # 2: Если я добавлю что-нибудь после чтения, обработчик работает. Таким образом, это не удается:

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

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

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

Вот что напечатано: "Совершено чтение"

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

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

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

+5

Показать код для вашего dostuff(), потому что этот код должен работать (и он делает) – user225312

+0

Он работает как ожидается с Python 2.5.1. – khachik

+4

, воспроизведенный с помощью Python 2.7, заменяя 'dostuff()' на 'sys.stdin.read()' – balpha

ответ

0

Вот предположение о том, что происходит: (? По какой-либо причине ... ошибка Win32 ограничение)

  • нажав Ctrl-C разрывает "печать" заявление
  • нажав Ctrl-C также бросает first KeyboardInterrupt, in dostuff()
  • Обработчик исключений запускается и пытается напечатать «Прервано», но оператор «print» сломан и выдает другой KeyboardInterrupt.
  • Предложение finally работает и пытается распечатать «очистка ....», но оператор «print» сломан и выдает еще один KeyboardInterrupt.
+0

Удалось ли воспроизвести проблему? Windows я предполагаю? – user225312

+1

Исключение не может «сломать» 'print' (или любую другую функцию, если на то пошло). Кроме того, это приведет к тому, что n-times-stacked будет «при обработке вышеупомянутого исключения, между которыми произошло другое исключение». – delnan

18

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

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

Этот тривиальный случай работал нормально с питона 2.6.6 (x64) интерактивный + 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. Он может принимать несколько инструкций байткода до того, как исключение async фактически будет вытолкнуто, а затем поднято в текущем контексте выполнения. (Это поведение, которое я видел, когда играл с ним в прошлом)

Смотрите C API: http://docs.python.org/c-api/init.html#PyThreadState_SetAsyncExc

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

>>> 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() видит сигнал и поруки, но он повторно поднимает сигнал после снятия с регистрации его обработчика. Я не знал бы точно, не проверив код интерпретатора.

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

EDIT 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()». (Там может быть несколько ошибок в игре)

+0

Но другие (включая меня - Windows 7 32 бит с Python3 здесь) воспроизвели его с заменой 'dostuff', которые не захватывают KeyboardInterrupt. – delnan

+0

@ delnan - ответ расширен. надеюсь, что это помогает в формулировании некоторых теорий! :) –

+4

Я бы не назвал это «нюансом» в асинхронных исключениях настолько, насколько просто ошибка. Если исключение поднимается чуть позже, чем когда это происходит технически, это нормально ... но если исключение возникает вне блока try, из которого исключение вышло, это еще одна история. Здесь четко описаны некоторые хорошо документированные формы поведения, а главное, что «окончательный» блок не запускает «гарантированный» код очистки; а также, что обработчик исключений не запускается вообще, когда по крайней мере одно исключение однозначно произошло в блоке «try». – Josh

0

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

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

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!" 

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

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

там не появляется быть проблемой.

dostuff() чтение из stdin?

+0

Я думаю, что вы на что-то за исключением того, что если вы замените sys.stdin.closed любой другой командой, включая печать или сон, это также заставляет ее работать. Я не думаю, что доступ к stdin во второй раз необходим ... Я думаю, что, возможно, нажатие ctrl-c заставляет системный вызов возвращаться, а затем ALSO отправляет прерывание программному обеспечению, но python не обрабатывает прерывание до тех пор, пока следующая строка кода ... к тому времени она уже выходит из блока try! – Josh

+0

Правильно, но я не уверен, что вы делаете в своем 'dostuff()', что также не могло бы привести к этому. – milkypostman

1

Имея подобную проблему, и это мой обходной путь:

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() 
0
def foo(): 
    try: 
     x=0 
     while 1: 
      x+=1 
      print (x) 
    except KeyboardInterrupt: 
     print ("interrupted!!") 
foo() 

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