反序列化有时会产生空对象

1 投票
1 回答
964 浏览
提问于 2025-04-17 14:38

我正在尝试使用pickle来保存一个自定义类,代码大概是下面这样的(虽然这个类里定义了几个方法,还有几个字典和其他数据)。但是,每当我运行这个代码,使用pickle保存然后再读取的时候,我发现类里的数据都没了,就好像我创建了一个新的空实例一样。

import pickle
class MyClass:
    VERSION = 1
    some_data = {}
    more_data = set()

    def save(self,filename):
        with open(filename, 'wb') as f:
            p = pickle.Pickler(f)
            p.dump(self)

    def load(filename):
        with open(filename,'rb') as ifile:
            u = pickle.Unpickler(ifile)
            obj = u.load()
            return obj

我在想这是否和pickle类的记忆有关,但我觉得应该不是。当它不工作的时候,我查看生成的文件,发现里面的内容大概是这样的:(显然不是为了让人看懂,但里面明显没有数据)

€c__main__
MyClass
q

总之,我希望这些信息能帮助别人理解这里可能发生了什么,或者该关注什么。

1 个回答

8

你遇到的问题是,你在用可变的类变量来存储数据,而不是把数据放到实例变量里。

pickle模块只会保存直接存储在实例上的数据,而不会保存可以通过self访问的类变量。当你发现反序列化后的实例没有数据时,这通常意味着类没有保存之前运行时的数据,所以实例就无法再访问这些数据了。

这样使用类变量可能还会引发其他问题,因为这些数据会被类的所有实例共享!下面是一个Python控制台的代码示例,说明了这个问题:

>>> class Foo(object):
        class_var = []
        def __init__(self, value):
            self.class_var.append(value)

>>> f1 = Foo(1)
>>> f1.class_var
[1]
>>> f2 = Foo(2)
>>> f2.class_var
[1, 2]

这可能不是你想要的结果。不过情况还会更糟!

>>> f1.class_var
[1, 2] 

你以为属于f1的数据其实被创建f2时改变了。实际上,f1.class_varf2.class_var是同一个对象(它也可以直接通过Foo.class_var访问,而不需要通过任何实例)。

所以,使用类变量几乎肯定不是你想要的。相反,你应该为这个类写一个__init__方法,创建一个新的值并将其保存为实例变量:

>>> class Bar(object):
        def __init__(self, value):
            self.instance_var = [] # creates a separate list for each instance!
            self.instance_var.append(value)

>>> b1 = Bar(1)
>>> b1.instance_var
[1]
>>> b2 = Bar(2)
>>> b2.instance_var # doesn't include value from b1
[2]
>>> b1.instance_var # b1's data is unchanged
[1]

这样,pickle会像你预期的那样处理这个类。它的所有数据都在实例里,所以当你反序列化时,实例不会变成空的。

撰写回答