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异常都没有捕捉到这个情况,尽管我确定此时程序正处于循环中(所以至少是在大的try/except块里面)。
这怎么可能呢?起初我以为是因为我使用的某个库没有正确处理异常(比如只用“except:”),但如果真是这样,我的脚本就不会停止了。但脚本确实停止了,而且应该至少被我的一个except捕捉到,对吧?
我哪里搞错了?
谢谢!
编辑:
在try-except后面加上一个 finally:
语句,并在两个try-except块中打印回溯信息,通常当我按下 Ctrl+C 时会显示 None
,但我曾经成功捕捉到这个(看起来是来自urllib2,但我不知道这是否是我无法捕捉到KeyboardInterrupt的原因):
回溯(最近的调用最后):
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>
2 个回答
很可能你在脚本不在尝试块(try block)里的时候按了CTRL-C,所以它没有捕捉到这个信号。
我在问题的评论中已经提到,这个问题可能是因为问题中省略的代码部分造成的。不过,具体的代码其实并不重要,因为在正常情况下,当你按下Ctrl-C中断Python代码时,Python应该会抛出一个KeyboardInterrupt
异常。
你在评论中提到你使用了boilerpipe
这个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
调试时发现,如果导入了boilerpipe
,Python会启动大量的线程:
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)]
没有导入boilerpipe
的gdb
会话:
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的处理出现问题。
编辑:作为一个可能的解决办法,你可以注册一个信号处理器,来捕捉当你按下Ctrl-C时进程接收到的SIGINT
信号。即使导入了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"