如何使具有多个 init args 的自定义异常类可腌制

Posted

技术标签:

【中文标题】如何使具有多个 init args 的自定义异常类可腌制【英文标题】:How to make a custom exception class with multiple init args pickleable 【发布时间】:2013-04-21 03:05:15 【问题描述】:

为什么我下面的自定义异常类没有使用 pickle 模块正确序列化/反序列化?

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 时,不会出现这个问题。

感谢您的帮助。 凯尔

编辑:修复我对 super per shx2 的调用 编辑:清理标题/内容

【问题讨论】:

【参考方案1】:

我只是这样做

class MyCustomException(Exception):
    def __init__(self):
        self.value = 'Message about my error'

    def __str__(self):
        return repr(self.value)

... somewhere in code ...
raise MyCustomException

【讨论】:

【参考方案2】:

如果您同时使用这两个参数来构造错误消息以传递给父 Exception 类,则当前的答案会失效。我相信最好的方法是在您的异常中简单地覆盖 __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__here的信息。

【讨论】:

这是最好的答案。另外两个真的只是黑客那种工作。这个可以正确构建类。 @borgr 抱歉,我不得不恢复您的编辑。用self.__class__ 调用super 是一种不好的做法,可能会破坏继承。请参阅此要点:gist.github.com/ulope/1935894 @sean 如果有些人希望能够继承,那么正确的做法是什么?如果你不能继承(或者你必须覆盖所有东西),它就会失去很多类的力量。而且我不明白为什么在减少它是一个问题,链接的错误只是因为动态变化的超类而发生。 @Sean 我什至没有案例:-) 我只是想知道用类名硬编码的函数(在此处减少)创建一个类是否不是一个坏习惯,所以不能被继承,不硬编码类名有什么缺点吗? @borgr,父类被子类继承,所以在方法中引用父类是正确的做法:class MySubException(MyException): pass。在MySubException中,当调用__reduce__时,由于MySubException并没有覆盖该方法,所以调用的其实是MyException.__reduce__(obj),而这个方法是指MyExceptionsuper类,即Exception。长话短说:别担心。【参考方案3】:

我喜欢 Martijn 的回答,但我认为更好的方法是将所有参数传递给 Exception 基类:

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

Exception 基类'__reduce__ 方法将包含所有参数。通过不将所有额外参数设为可选,您可以确保正确构造异常。

【讨论】:

然后根据需要覆盖__str__【参考方案4】:

使arg2 可选:

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

Exception 基类定义了一个.__reduce__() method 以使扩展(基于C)类型可picklable,并且该方法只需要一个 参数(即.args);见BaseException_reduce() function in the C source。

最简单的解决方法是将额外的参数设为可选。 __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'

【讨论】:

以上是关于如何使具有多个 init args 的自定义异常类可腌制的主要内容,如果未能解决你的问题,请参考以下文章

创建具有多个标签的自定义按钮

具有多个 UILabel 和图像的自定义按钮

如何在具有多个参数的自定义函数中使用 mutate

您如何使用具有多个突变的自定义文件正确播种 prisma DB?

如何使子类化的自定义 Django 表单字段不再是必需的?

具有多个子项目的项目列表的自定义适配器?