捕获不可反序列化的异常并重新引发
这是我之前提问的后续内容,关于在使用SQLAlchemy和多进程时Python脚本挂起的问题。在那个问题中,我们讨论了在Python中处理异常时遇到的麻烦。通常这不是个大问题,但在使用Python的多进程模块时就会出现问题。因为多进程是通过“序列化”来传递对象的,如果在多进程的某个进程中发生错误,整个进程可能会挂起,就像在那个问题中展示的那样。
一种可能的解决办法是修复所有有问题的异常,正如在那个问题中讨论的那样。但这并不简单,因为我们无法提前知道哪些异常会被调用。另一种方法是lbolla在回答中提到的,就是捕获异常,构造一个无害的等效异常,然后再抛出。不过,我不太确定具体该怎么做。我们来看下面的代码。
class BadExc(Exception):
def __init__(self, message, a):
'''Non-optional param in the constructor.'''
Exception.__init__(self, message)
self.a = a
import sys
try:
try:
#print foo
raise BadExc("bad exception error message", "a")
except Exception, e:
raise Exception(e.__class__.__name__ + ": " +str(e)), None, sys.exc_info()[2]
except Exception, f:
pass
import cPickle
a = cPickle.dumps(f)
l = cPickle.loads(a)
print "raising error"
raise sys.exc_info()[0], sys.exc_info()[1], sys.exc_info()[2]
这段代码对异常进行了序列化和反序列化,然后抛出它,结果出现了错误
raising error
Traceback (most recent call last):
File "<stdin>", line 11, in <module>
Exception: BadExc: bad exception error message
感谢Glenn Maynard对“Python中的‘内部异常’(带回溯)”的回答。这其中包含了重要的信息,比如回溯、错误信息和异常类型,所以这可能是我们能做到的最好效果。但理想情况下,我希望得到的异常看起来和原始异常一模一样,也就是
Traceback (most recent call last):
File "<stdin>", line 11, in <module>
__main__.BadExc: bad exception error message
或者更一般来说,异常的名称应该放在前面,而不是Exception
。这样做有可能吗?
另外,除了BadExc
类,我们还可以使用print foo
语句,这样会产生一个NameError
。不过,这个异常不需要特别处理。
1 个回答
你可以通过重写 sys.excepthook
来实现你想要的效果。这个方法在这个例子中至少是有效的,但它有点儿不太正规,所以请自己测试一下,不能保证一定好用 :-)
import sys
def excepthook_wrapper(type, value, traceback):
if len(value.args) == 2:
name, msg = value.args
value.args = (msg,)
sys.__excepthook__(name, value, traceback)
else:
sys.__excepthook__(type, value, traceback)
sys.excepthook = excepthook_wrapper
(补充:我其实对这个方法不是特别满意,因为现在“正常”的异常如果有两个参数也会被不同处理。一个可能的解决办法是,给你的特殊异常加个标签,传入“PICKLED”作为第一个参数,然后检查这个标签,而不是检查 args
的长度。)
然后创建这个 Exception
,用两个参数,一个是名字(__module__.__class__
),另一个是异常信息(str(e)
):
try:
try:
#print foo
raise BadExc("bad exception error message", "a")
except Exception, e:
cls = e.__class__
if hasattr(cls, '__module__'):
name = '{0}.{1}'.format(cls.__module__, cls.__name__)
else:
name = cls.__name__
raise Exception(name, str(e)), None, sys.exc_info()[2]
except Exception, f:
pass
接下来这个:
import cPickle
a = cPickle.dumps(f)
l = cPickle.loads(a)
print "raising error"
raise sys.exc_info()[0], sys.exc_info()[1], sys.exc_info()[2]
会打印出:
raising error
Traceback (most recent call last):
File "test.py", line 18, in <module>
raise BadExc("bad exception error message", "a")
__main__.BadExc: bad exception error message