捕获不可反序列化的异常并重新引发

5 投票
1 回答
2139 浏览
提问于 2025-04-17 10:33

这是我之前提问的后续内容,关于在使用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 个回答

2

你可以通过重写 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

撰写回答