Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
305 views
in Technique[技术] by (71.8m points)

python - Cannot unpickle Exception subclass

Simplified version of Why is my custom exception unpickle failing.

I am trying to pickle a 'simple' exception subclass. It pickles OK, but when unpickling it falls over:

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

Error:

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)

An earlier comment suggested the issue is because the built in Exception class supplies a __setstate_() method. However, it's not clear to me if this is expected behaviour or not - it certainly seems surprising since doing the same thing with a subclass of object works OK.

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

The BaseException class defines a custom __reduce__ method in exceptions.c, which returns the list of arguments to pass to __init__. Exact code is

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);

According to __reduce__ documentation,

  • the first item of the tuple is the callable to invoke. Here, that will be the exception class.
  • the second item is the tuple of arguments to pass to the callable. Here, that will be self.args.
  • the third item is a dict to merge into self.__dict__.

So from this, BaseException.__reduce__ will make unpickle invoke the exception's constructor with given args.

You have two options: either override __reduce__, or put the required arguments in self.args, either directly or by letting the parent class do it:

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

Note that the original issue comes from the rather naive way BaseException pickle handling works. It is fixed in the latest python3 releases. Your question's original code works fine on python 3.5 for instance.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...