'Why is my custom exception unpickle failing

import pickle

class ABError(Exception):
    def __init__(self, a, b):
        super(ABError, self).__init__(a)
        self.a = a
        self.b = b

class ABCDError(ABError):
    def __init__(self, a, b, c, d):
        super(ABCDError, self).__init__(a, b)
        self.c = c
        self.d = d

err = ABCDError("aaaaa", "bbbbb", "ccccc", "ddddd")

pickled_err = pickle.dumps(err)

original_err = pickle.loads(pickled_err)  # Fails

I get the following traceback:

Traceback (most recent call last):
  File "pickle_pain.py", line 19, in <module>
    original_err = pickle.loads(pickled_err)  # 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 5 arguments (2 given)

I've done some googling, but struggling to find a good answer for why this happens, and not desperate to start learning the pickle serialisation format in order to step through this :-/

Edit: The behaviour is the same in python3, though the error message is slightly nicer:

Traceback (most recent call last):
  File "pickle_pain.py", line 19, in <module>
    original_err = pickle.loads(pickled_err)  # Fails
TypeError: __init__() missing 3 required positional arguments: 'b', 'c', and 'd'


Solution 1:[1]

I was answering a different question that linked to this one and they are kind of the same. reduce will return how to recreate the object and it bases it off of whats in self.args. Since you aren't passing all the args to Exception it breaks. If you need to be able to access the custom fields without doing e.args[0] for a and e.args[1] for b etc, just make properties. Notice the Extended class still calls the Exception init not ABError.init.

import pickle

class ABError(Exception):
    def __init__(self, a, b):
        super(ABError, self).__init__(a, b)
        
    @property
    def a(self):
        return self.args[0]

    @property
    def b(self):
        return self.args[1]


class ABCDError(ABError):
    def __init__(self, a, b, c, d):
        super(ABError, self).__init__(a, b, c, d)

    @property
    def c(self):
        return self.args[2]

    @property
    def d(self):
        return self.args[3]



err = ABCDError("aaaaa", "bbbbb", "ccccc", "ddddd")

pickled_err = pickle.dumps(err)

original_err = pickle.loads(pickled_err)  # Fails

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Nathan Buckner