捕获无法pickle的异常并重新引发

5
这是我之前提出的问题 Hang in Python script using SQLAlchemy and multiprocessing 的跟进。正如在那个问题中讨论的那样,将异常进行pickling在Python中是有问题的。这通常不是一个问题,但有一种情况,当Python多进程模块中发生错误时,它就会成为一个问题。由于多进程通过pickling移动对象,如果在多进程过程中发生错误,则整个进程可能会挂起,就像那个问题中所演示的那样。
一种可能的方法是修复所有有问题的异常,就像在那个问题中讨论的那样。这并不容易,因为人们很难预先知道哪些异常可能被调用。另一种方法是捕获异常,构造等效的无害异常,然后重新抛出异常,正如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。这可行吗?
或者,可以使用 print foo 语句代替 BadExc 类,这会导致 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__)和Exception消息(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

嗨,Rob。感谢你的回答。为你的努力加一分,但我真的不喜欢使用像sys.excepthook这样的“全局”东西来解决这个问题,正如你所指出的,它有问题。我曾经模糊地想过,也许有一种方法可以通过覆盖某些属性或方法来欺骗解释器,使其认为抛出的异常是BadExc类型,但我不知道是否可以实现,也不知道如何实现。 - Faheem Mitha

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接