python - Python 3.7 中的 Pickle 重大变化

标签 python python-3.x pickle

我的自定义列表和字典类在 Python 3.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))

在 Python 2.6 到 3.6 上工作正常:

"C:\Program Files\Python36\python.exe" issue.py
True

但在 3.7 中不再正确设置 self.option

"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 在那一点上仍然未定义。

我必须直接从dictlist 继承,而且我确实需要覆盖appendextend 我的代码中的函数。有没有办法预先设置 option 或其他修复方法?是否记录了这种行为变化及其合理性?

谢谢你的时间

最佳答案

Unpickling 列表对象 switched from using list.append() to list.extend() ,因为对于某些 list 子类来说,这可能会更快。

但是,有了这个改变,测试列表对象的 unpickling 代码的方式也改变了,从

if (PyList_Check(list)) {

if (PyList_CheckExact(list)) {

正是这种改变影响了您的代码。上面的测试寻找一个快速路径,说 如果我们有一个列表类,然后使用 PyList_SetSlice() 加载数据,而不是显式调用 的较慢路径.extend().append() 新实例上的方法。旧版本(Python 3.6 及更早版本)接受列表和子类,新版本只接受list 本身,不接受子类!

因此,对于 Python 3.6 及更早版本,当取消选中您的自定义 MyList.append() 方法时,不会调用,这纯粹是因为您子类化了 list .在 Python 3.7 中,当您的自定义 MyList.extend() 方法被 unpickling 时, 调用。这是非常有意的,子类应该被允许提供自定义的 .extend() 方法,该方法在 unpickling 时被调用。

解决方法很简单。您的数据在解封时已经包装,您不需要重新应用该包装。当您没有设置 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)

这一切确实意味着您不能依赖任何已恢复的实例属性。如果这是一个大问题(你仍然需要在 unpickling 时查询实例状态),那么你将不得不提供一个不同的 __reduce_ex__ method ,它不会将数据作为结果元组索引 3 中的迭代器返回。 list().__reduce_ex__() 对于协议(protocol)版本 2、3 和 4 返回 (copyreg.__newobj__, type(self), self.__dict__, iter(self), None).

例如,自定义版本必须使用 (type(self), (tuple(self), self.option), None, None, None)。这确实会带来一些额外的开销(tuple(self) 在 pickling 和 unpickling 时会占用额外的内存)。

关于python - Python 3.7 中的 Pickle 重大变化,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/52333864/

相关文章:

python - 序列化具有依赖项的 python 函数

python - write() 参数必须是 str,而不是 bytes

python - 启动具有多个独立项目的 TRAC 服务器

python - 如何在类中迭代 `dict` 就像只引用 `dict` 一样?

python - Python 中的 Xpath 。获取语​​法错误 ("invalid predicate")

django - 如何使用 Postman 使用参数命中 Django api?

python - 在 python 中读取 csv 文件时跳过几行

python - 使用 to_pickle 在循环中保存多个数据帧

python - vscode中从一个文件到另一个文件的自动导入和重构(移动)功能

python - 子字符串中的模式限制 - Python