Skip to main content
 首页 » 编程设计

python之无法解开异常子类

2025年05月04日14zfyouxi

Why is my custom exception unpickle failing 的简化版本.

我正在尝试挑选一个“简单”的异常子类。它可以腌制,但在去除腌制时它会掉落:

import pickle 
 
class ABError(Exception): 
    def __init__(self, a, b): 
        self.a = a 
        self.b = b 
 
ab_err = ABError("aaaa", "bbbb") 
 
pickled = pickle.dumps(ab_err) 
original = pickle.loads(pickled)  # Fails 

错误:

Traceback (most recent call last): 
  File "p.py", line 12, in <module> 
    original = pickle.loads(pickled)  # Fails 
  File "/usr/lib/python2.7/pickle.py", line 1388, in loads 
    return Unpickler(file).load() 
  File "/usr/lib/python2.7/pickle.py", line 864, in load 
    dispatch[key](self) 
  File "/usr/lib/python2.7/pickle.py", line 1139, in load_reduce 
    value = func(*args) 
TypeError: __init__() takes exactly 3 arguments (1 given) 

较早的评论表明该问题是因为内置的 Exception 类提供了 __setstate_() 方法。但是,我不清楚这是否是预期的行为 - 这看起来确实令人惊讶,因为对 object 的子类做同样的事情可以正常工作。

请您参考如下方法:

BaseException 类定义了一个自定义 __reduce__ exceptions.c 中的方法,它返回要传递给 __init__ 的参数列表。确切的代码是

if (self->args && self->dict) 
    return PyTuple_Pack(3, Py_TYPE(self), self->args, self->dict); 
else 
    return PyTuple_Pack(2, Py_TYPE(self), self->args); 

根据__reduce__文档,

  • 元组的第一项是要调用的可调用对象。在这里,这将是异常类。
  • 第二项是传递给可调用对象的参数元组。在这里,它将是 self.args。
  • 第三项是要合并到 self.__dict__ 中的字典。

因此,BaseException.__reduce__ 将使 unpickle 使用给定的参数调用异常的构造函数。

您有两个选择:要么覆盖 __reduce__,要么将所需的参数直接放入 self.args 中,或者让父类这样做:

import pickle 
 
class ABError(Exception): 
    def __init__(self, a, b): 
        self.a = a 
        self.b = b 
        # self.args = (a, b) 
        # maybe better, let base class's __init__ do it => 
        super(ABError, self).__init__(a, b) 
 
ab_err = ABError("aaaa", "bbbb") 
 
pickled = pickle.dumps(ab_err) 
original = pickle.loads(pickled)  # no longer fails 

请注意,最初的问题来自于 BaseException pickle 处理的相当幼稚的方式。它在最新的 python3 版本中得到修复。例如,您问题的原始代码在 python 3.5 上运行良好。