如何使具有多个 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)
,而这个方法是指MyException
的super
类,即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 的自定义异常类可腌制的主要内容,如果未能解决你的问题,请参考以下文章