不能破坏Exception子类

时间:2017-01-23 14:40:57

标签: python

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的子类做同样的事情就行了。

1 个答案:

答案 0 :(得分:4)

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__文档,

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

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

您有两个选择:覆盖__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泡菜处理工作的相当天真的方式。它是在最新的python3版本中修复的。例如,您的问题的原始代码在python 3.5上运行正常。