我有自定义list和字典类,在Python3.7中,这些类不再起作用。
import pickle
class A(dict):
pass
class MyList(list):
def __init__(self, iterable=None, option=A):
self.option=option
if iterable:
for x in iterable:
self.append(x)
def append(self, obj):
if isinstance(obj, dict):
obj = self.option(obj)
super(MyList, self).append(obj)
def extend(self, iterable):
for item in iterable:
self.append(item)
if __name__ == '__main__':
pickle_file = 'test_pickle'
my_list = MyList([{'a': 1}])
pickle.dump(my_list, open(pickle_file, 'wb'))
loaded = pickle.load(open(pickle_file, 'rb'))
print(isinstance(loaded[0], A))
Python2.6到3.6运行良好:
"C:\Program Files\Python36\python.exe" issue.py
True
但不再正确地将self.option
设置为3.7。
"C:\Program Files\Python37\python.exe" issue.py
Traceback (most recent call last):
File "issue.py", line 28, in <module>
loaded = pickle.load(open(pickle_file, 'rb'))
File "issue.py", line 21, in extend
self.append(item)
File "issue.py", line 16, in append
obj = self.option(obj)
AttributeError: 'MyList' object has no attribute 'option'
但是,如果我要删除extend
函数,它就会像预期的那样工作。
我也尝试过添加__setstate__
,但是在extend
之前没有调用它,因此option
在那时仍然没有定义。
我确实必须直接从dict
和list
继承,我确实需要在代码中覆盖append
和extend
函数。是否有一种预先设置option
或另一种修复的方法?这种行为的改变是否记录在案,是否合理?
谢谢您抽时间见我
发布于 2018-09-14 07:15:21
取消列表对象list.extend()
,因为对于某些list
子类来说,这样做要快得多。
但是,随着这一变化,列表对象的未筛选代码测试的方式也发生了变化,
if (PyList_Check(list)) {
至
if (PyList_CheckExact(list)) {
正是这种改变影响了您的代码。上面的测试寻找一个快速路径,也就是说,如果我们有一个list类,那么使用PyList_SetSlice()
加载数据,而不是显式调用新实例上的.extend()
或.append()
方法的较慢路径。旧版本(Python3.6及更高版本)接受列表和子类,新版本只接受list
本身,而不是子类!
因此,对于Python3.6和更高版本,在解压缩自定义MyList.append()
方法时,并不称为,这完全是因为您对list
进行了子类化。在Python3.7中,当取消自定义MyList.extend()
方法时,调用是。这在很大程度上是有意的,应该允许子类提供一个自定义的.extend()
方法,该方法在不进行腌制时会被调用。
解决问题很简单。您的数据已经包装好了,您不需要重新应用该包装器。当您没有self.option
集时,只需跳过应用
def append(self, obj):
if isinstance(obj, dict):
try:
obj = self.option(obj)
except AttributeError:
# something's wrong, are we unpickling on Python 3.7 or newer?
if 'option' in self.__dict__:
# no, we are not, because 'option' has been set, this must
# be an error in the option() call, so re-raise
raise
# yes, we are, just ignore this, obj is already wrapped
super(MyList, self).append(obj)
这一切都意味着您不能依赖任何已经恢复的实例属性。如果这是一个很大的问题(您仍然需要在解析过程中查询实例状态),那么您必须提供一个不同的 method,它不会将数据作为迭代器返回到结果元组的索引3中。协议版本2、3和4的list().__reduce_ex__()
返回(copyreg.__newobj__, type(self), self.__dict__, iter(self), None)
。
例如,定制版本必须使用(type(self), (tuple(self), self.option), None, None, None)
。这确实带来了一些额外的开销(当tuple(self)
进行腌制和不腌制时,它将占用额外的内存)。
https://stackoverflow.com/questions/52333864
复制