Ctrl-C завершает мой скрипт, но он не перехватывается исключением KeyboardInterrupt

У меня есть скрипт Python, который содержит большой цикл, читающий файл и выполняющий некоторые действия (я использую несколько пакетов, таких как urllib2, httplib2 или BeautifulSoup).

Это выглядит так:

try:
    with open(fileName, 'r') as file :
        for i, line in enumerate(file):
            try:
                # a lot of code
                # ....
                # ....
            except urllib2.HTTPError:
                print "\n >>> HTTPError"
            # a lot of other exceptions
            # ....
            except (KeyboardInterrupt, SystemExit):
                print "Process manually stopped"
                raise
            except Exception, e:
                print(repr(e))
except (KeyboardInterrupt, SystemExit):
    print "Process manually stopped"
    # some stuff

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

Как это возможно? Сначала я подумал, что это из-за того, что один из пакетов, которые я использую, неправильно обрабатывает исключения (например, используя только «исключение:»), но если бы это было так, мой скрипт не остановился бы. Но скрипт ДЕЙСТВИТЕЛЬНО останавливается, и он должен быть пойман хотя бы одним моим двумя, кроме, верно?

Где я не прав?

Заранее спасибо!

ИЗМЕНИТЬ:

С добавлением предложения finally: после try-except и печатью трассировки в обоих блоках try-except обычно отображается None, когда я нажимаю Ctrl+C, но однажды мне удалось чтобы получить это (кажется, что это исходит от urllib2, но я не знаю, является ли это причиной, по которой я не могу поймать KeyboardInterrupt):

Traceback (последний последний вызов):

File "/home/darcot/code/Crawler/crawler.py", line 294, in get_articles_from_file
  content = Extractor(extractor='ArticleExtractor', url=url).getText()
File "/usr/local/lib/python2.7/site-packages/boilerpipe/extract/__init__.py", line 36, in __init__
  connection  = urllib2.urlopen(request)
File "/usr/local/lib/python2.7/urllib2.py", line 126, in urlopen
  return _opener.open(url, data, timeout)
File "/usr/local/lib/python2.7/urllib2.py", line 391, in open
  response = self._open(req, data)
File "/usr/local/lib/python2.7/urllib2.py", line 409, in _open
  '_open', req)
File "/usr/local/lib/python2.7/urllib2.py", line 369, in _call_chain
  result = func(*args)
File "/usr/local/lib/python2.7/urllib2.py", line 1173, in http_open
  return self.do_open(httplib.HTTPConnection, req)
File "/usr/local/lib/python2.7/urllib2.py", line 1148, in do_open
  raise URLError(err)
URLError: <urlopen error [Errno 4] Interrupted system call>

person Thematrixme    schedule 12.08.2014    source источник
comment
Код, который вы вставили, кажется довольно чистым. Я также пытался выполнить вложенный Try-Catch. Работает отлично. Возможно, виновник находится внутри части кода. NOTE: Избегайте вложенных TRY-CATCH.   -  person Yogeesh Seralathan    schedule 12.08.2014
comment
Почему вы дважды ловите исключение KeyboardInterrupt? Какую версию питона вы используете? 2.7.8? Я попробовал что-то похожее на ваш пример с python 3.4, и он отлично работает...   -  person flammi88    schedule 12.08.2014
comment
@ flammi88 Я поймал это дважды, потому что не был уверен, что это будет поймано исключением исключения, например. Но этого не должно быть, и большой попытки try-except должно быть достаточно, чтобы поймать исключение.   -  person Thematrixme    schedule 12.08.2014
comment
И я использую Python 2.7   -  person Thematrixme    schedule 12.08.2014
comment
Вы вообще получаете журнал трассировки?   -  person Yoel    schedule 12.08.2014
comment
И я знаю, что так должно работать. Я попробовал более простую версию этого, без всего моего кода в цикле, и он работал нормально. Таким образом, проблема, похоже, связана с одним из пакетов, которые я использую, но чтобы найти проблему, я хотел бы знать, что может ее вызвать. Потому что я даже не могу придумать, как остановить скрипт, не вызывая этого исключения...   -  person Thematrixme    schedule 12.08.2014
comment
@Yoel Нет, я просто вижу ^C на терминале, и программа останавливается.   -  person Thematrixme    schedule 12.08.2014
comment
Я опробовал код, который вы предоставили с python 2.7.8, и уловил, что исключение KeyboardInterrupt работает хорошо. Я заменил первый комментарий «много кода» оператором pass, чтобы заставить его работать, и добавил несколько дополнительных отпечатков, чтобы увидеть, какие исключения запускаются... С моей точки зрения, ваша проблема выходит из блока, который вы пропустили. .   -  person flammi88    schedule 12.08.2014
comment
@ flammi88 Да, я тоже так думаю, но как вообще возможно, чтобы Ctrl-C останавливал программу, не вызывая этого исключения?   -  person Thematrixme    schedule 12.08.2014
comment
Я могу представить случаи, но трудно сказать без какой-либо информации о том, что вы пытаетесь сделать в разделе «много кода»? Может какая-то многопоточность? (Я не уверен, как python реагирует в этих случаях, я никогда раньше не выполнял многопоточность в python...)   -  person flammi88    schedule 12.08.2014
comment
Попробуйте добавить предложение finally и распечатать там трассировку...   -  person Yoel    schedule 12.08.2014
comment
@ flammi88 Нет многопоточности (по крайней мере, я не знаю, она может быть в пакетах, которые я использую). По сути, я просто зацикливаю файл, содержащий URL-адреса газетных статей, и для каждого URL-адреса я извлекаю статью (используя такие пакеты, как BeautifulSoup, газета, бойлерпайп, urllib2, httplib2) и вставляю ее в базу данных. Я думаю, нет ничего странного.   -  person Thematrixme    schedule 12.08.2014
comment
@Yoel Должен ли я поставить finally после обоих исключений?   -  person Thematrixme    schedule 12.08.2014
comment
Достаточно вставить его во внутренний блок try-except.   -  person Yoel    schedule 12.08.2014
comment
@Yoel Йоэль Мне удалось кое-что получить, но, поскольку я получил это только один раз, я не уверен, что это причина моей проблемы. Пожалуйста, посмотрите, как я редактировал сообщение.   -  person Thematrixme    schedule 12.08.2014
comment
Я не уверен, связана ли ваша проблема с трассировкой стека urllib2. Я предполагаю, что ваша проблема вызвана одним из ваших нечистых пакетов python... Если вы действительно хотите, вы можете отладить это с помощью gdb --args python ‹Your scriptname› здесь далее. Gdb сбросит оболочку, когда вы нажмете ctrl-c, и вы сможете получить C-Backtrace, когда введете туда bt... (Я должен признать, что это может быть излишним для решения вашей проблемы, но у меня нет лучшей идеи)   -  person flammi88    schedule 12.08.2014
comment
Еще один вопрос: на какой платформе вы это разрабатываете? Линукс/Виндовс/Мак? Я предположил, что Linux в моем предыдущем комментарии о других платформах, отлаживающих python, может работать по-другому...   -  person flammi88    schedule 12.08.2014
comment
Как вы напечатали трассировку? Вы вызывали traceback.print_stack()?   -  person Yoel    schedule 12.08.2014
comment
@Yoel Да, я использовал это, и большую часть времени он печатал только None, мне только однажды удалось получить что-то еще (то, что я добавил в пост), но, как предположил @flammi88, это похоже на проблему JPype.   -  person Thematrixme    schedule 12.08.2014


Ответы (2)


Я уже предположил в своих комментариях к вопросу, что эта проблема, вероятно, вызвана разделом кода, который не указан в вопросе. Однако точный код не должен иметь значения, поскольку Python обычно должен вызывать исключение KeyboardInterrupt, когда код Python прерывается нажатием Ctrl-C.

Вы упомянули в комментариях, что используете пакет boilerpipe Python. Этот пакет Python использует JPype для создания привязки языка к Java... Я могу воспроизвести вашу проблему с помощью следующей программы Python:

from boilerpipe.extract import Extractor
import time

try:
  for i in range(10):
    time.sleep(1)

except KeyboardInterrupt:
  print "Keyboard Interrupt Exception"

Если вы прервете эту программу с помощью Ctrl-C, исключение не будет сгенерировано. Кажется, что программа немедленно завершается, оставляя интерпретатор Python без возможности генерировать исключение. При удалении импорта boilerpipe проблема исчезает...

Сеанс отладки с gdb указывает на то, что большое количество потоков было запущено Python, если импортировано boilerpipe:

gdb --args python boilerpipe_test.py
[...]
(gdb) run
Starting program: /home/fabian/Experimente/pykeyinterrupt/bin/python boilerpipe_test.py
warning: Could not load shared library symbols for linux-vdso.so.1.
Do you need "set solib-search-path" or "set sysroot"?
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/usr/lib/libthread_db.so.1".
[New Thread 0x7fffef62b700 (LWP 3840)]
[New Thread 0x7fffef52a700 (LWP 3841)]
[New Thread 0x7fffef429700 (LWP 3842)]
[New Thread 0x7fffef328700 (LWP 3843)]
[New Thread 0x7fffed99a700 (LWP 3844)]
[New Thread 0x7fffed899700 (LWP 3845)]
[New Thread 0x7fffed798700 (LWP 3846)]
[New Thread 0x7fffed697700 (LWP 3847)]
[New Thread 0x7fffed596700 (LWP 3848)]
[New Thread 0x7fffed495700 (LWP 3849)]
[New Thread 0x7fffed394700 (LWP 3850)]
[New Thread 0x7fffed293700 (LWP 3851)]
[New Thread 0x7fffed192700 (LWP 3852)]

gdb сессия без импорта boilerpipe:

gdb --args python boilerpipe_test.py
[...]
(gdb) r
Starting program: /home/fabian/Experimente/pykeyinterrupt/bin/python boilerpipe_test.py
warning: Could not load shared library symbols for linux-vdso.so.1.
Do you need "set solib-search-path" or "set sysroot"?
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/usr/lib/libthread_db.so.1".
^C
Program received signal SIGINT, Interrupt.
0x00007ffff7529533 in __select_nocancel () from /usr/lib/libc.so.6
(gdb) signal 2
Continuing with signal SIGINT.
Keyboard Interrupt Exception
[Inferior 1 (process 3904) exited normally 

Поэтому я предполагаю, что ваш сигнал Ctrl-C обрабатывается в другом потоке или что jpype делает другие странные вещи, которые нарушают обработку Ctrl-C.

EDIT: В качестве возможного обходного пути вы можете зарегистрировать обработчик сигнала, который перехватывает сигнал SIGINT, который процесс получает, когда вы нажимаете Ctrl-C. Обработчик сигнала запускается, даже если импортированы boilerpipe и JPype. Таким образом, вы будете получать уведомления, когда пользователь нажимает Ctrl-C, и вы сможете обрабатывать это событие в центральной точке вашей программы. Вы можете завершить скрипт, если хотите, в этом обработчике. Если вы этого не сделаете, сценарий продолжит работу с того места, где он был прерван, после возврата функции обработчика сигнала. См. пример ниже:

from boilerpipe.extract import Extractor
import time
import signal
import sys

def interuppt_handler(signum, frame):
    print "Signal handler!!!"
    sys.exit(-2) #Terminate process here as catching the signal removes the close process behaviour of Ctrl-C

signal.signal(signal.SIGINT, interuppt_handler)

try:
    for i in range(10):
        time.sleep(1)
#    your_url = "http://www.zeit.de"
#    extractor = Extractor(extractor='ArticleExtractor', url=your_url)
except KeyboardInterrupt:
    print "Keyboard Interrupt Exception" 
person flammi88    schedule 12.08.2014
comment
Я думаю, что это единственный разумный ответ, большое спасибо! Однако теперь я должен столкнуться с непростой проблемой, у вас есть идеи, как найти обходной путь? Может быть, с определением другой комбинации клавиш, которая позволит мне разорвать цикл и правильно выйти из программы, и это не будет странно обрабатываться JPype? - person Thematrixme; 13.08.2014
comment
Пожалуйста, посмотрите, может ли мой обновленный ответ помочь вам. Не заглядывая в код JPype, это единственное известное мне решение... Надеюсь, это поможет вам. - person flammi88; 13.08.2014
comment
Это определенно помогло мне, большое спасибо! Обходной путь работает отлично. - person Thematrixme; 13.08.2014

Скорее всего, вы нажимаете CTRL-C, когда ваш скрипт находится за пределами блока try и, следовательно, не перехватывает сигнал.

person Drudi    schedule 12.08.2014
comment
Я определенно уверен, что нажал Ctrl-C, когда программа находится в цикле (это нетрудно понять), и поскольку мой цикл находится внутри блока try, это не может быть правильным ответом. - person Thematrixme; 12.08.2014