Как создать собственный класс исключений с несколькими init args pickleable

Почему мой пользовательский класс исключений ниже не сериализуется/неэтериализован правильно, используя модуль рассола?

import pickle

class MyException(Exception):
    def __init__(self, arg1, arg2):
        self.arg1 = arg1
        self.arg2 = arg2

        super(MyException, self).__init__(arg1)

e = MyException("foo", "bar")

str = pickle.dumps(e)
obj = pickle.loads(str)

Этот код вызывает следующую ошибку:

Traceback (most recent call last):
File "test.py", line 13, in <module>
   obj = pickle.loads(str)
File "/usr/lib/python2.7/pickle.py", line 1382, in loads
   return Unpickler(file).load()
File "/usr/lib/python2.7/pickle.py", line 858, in load
   dispatch[key](self)
File "/usr/lib/python2.7/pickle.py", line 1133, in load_reduce
   value = func(*args)
TypeError: __init__() takes exactly 3 arguments (2 given)

Я уверен, что эта проблема проистекает из недостатка знаний о моей части того, как сделать классный сорт. Интересно, что эта проблема не возникает, когда мой класс не расширяет Exception.

Спасибо за любую помощь. Кайл

РЕДАКТИРОВАТЬ: Фиксация моего звонка на супер за shx2 EDIT: очистка заголовка/содержимого

Ответ 1

Сделайте arg2 необязательным:

class MyException(Exception):
    def __init__(self, arg1, arg2=None):
        self.arg1 = arg1
        self.arg2 = arg2
        super(MyException, self).__init__(arg1)

Базовый класс Exception определяет метод .__reduce__(), чтобы сделать тип расширения (на основе C) picklable, и этот метод ожидает только один аргумент (который равен .args); см. функцию BaseException_reduce() в источнике C.

Самый простой способ - сделать дополнительные аргументы необязательными. Метод __reduce__ также включает любые дополнительные атрибуты объекта за пределами .args и .message, и ваши экземпляры воссозданы должным образом:

>>> e = MyException('foo', 'bar')
>>> e.__reduce__()
(<class '__main__.MyException'>, ('foo',), {'arg1': 'foo', 'arg2': 'bar'})
>>> pickle.loads(pickle.dumps(e))
MyException('foo',)
>>> e2 = pickle.loads(pickle.dumps(e))
>>> e2.arg1
'foo'
>>> e2.arg2
'bar'

Ответ 2

Мне нравится ответ Martijn, но я думаю, что лучший способ - передать все аргументы базовому классу Exception:

class MyException(Exception):
    def __init__(self, arg1, arg2):
        super(MyException, self).__init__(arg1, arg2)        
        self.arg1 = arg1
        self.arg2 = arg2

В базовый метод Exception class '__reduce__ будут включены все аргументы. Не добавляя дополнительные аргументы необязательно, вы можете убедиться, что исключение построено правильно.

Ответ 3

Текущие ответы ломаются, если вы используете оба аргумента для создания сообщения об ошибке для перехода к родительскому классу исключений. Я считаю, что лучший способ - просто переопределить метод __reduce__ в вашем исключении. Метод __reduce__ должен возвращать двухфакторный кортеж. Первый элемент в кортеже - ваш класс. Второй элемент - это кортеж, содержащий аргументы, передаваемые вашему классу __init__.

import pickle

class MyException(Exception):
    def __init__(self, arg1, arg2):
        self.arg1 = arg1
        self.arg2 = arg2

        super(MyException, self).__init__('arg1: {}, arg2: {}'.format(arg1, arg2))

    def __reduce__(self):
        return (MyException, (self.arg1, self.arg2))


original = MyException('foo', 'bar')
print repr(original)
print original.arg1
print original.arg2

reconstituted = pickle.loads(pickle.dumps(original))
print repr(reconstituted)
print reconstituted.arg1
print reconstituted.arg2

Подробнее о __reduce__ здесь.