我有一个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:"only),但如果是这样的话,我的脚本不会停止。但是脚本确实停止了,并且它应该被至少一个(我的两个除外)捕获,对吗?
我错在哪里?
提前感谢!
编辑:在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>
我已经在对问题的评论中建议,这个问题很可能是由问题中遗漏的代码部分引起的。然而,确切的代码不应该是相关的,因为当Python代码被Ctrl-C中断时,Python通常会抛出KeyboardInterrupt
异常。
您在评论中提到您使用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
的调试会话表明,如果导入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的处理。
EDIT:作为一种可能的解决方案,您可以注册一个信号处理程序,该处理程序捕获进程在按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"
最有可能的是,当您的脚本在try块之外时,您发出了CTRL-C,因此没有捕捉到信号。