关于python:使用pickle加载类的状态

Use pickle to load a state for class

我想用泡菜把脚弄湿,所以我写了一个这样的示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
class start(tk.Frame):
    def __init__(self,*args,**kwargs):
        tk.Frame.__init__(self,*args,**kwargs)
        frame = tk.Frame(self,width=600,height=600)
        self.val = 0
        self.plusButton = tk.Button(self,text="plus",command=self.plus)
        self.plusButton.pack()
        self.valLabel = tk.Label(self)
        self.valLabel.pack()
        self.saveButton = tk.Button(self,text="save",command=self.save)
        self.saveButton.pack()
        self.loadButton = tk.Button(self,text="load",command=self.load)
        self.loadButton.pack()
    def load(self):
        self.__dict__ = pickle.load(open("testtesttest.p","rb" ))
    def plus(self):
        self.val += 1
        self.valLabel.config(text="%d"%(self.val))
    def save(self):
        pickle.dump(self.__getstate__, open("testtesttest.p","wb" ))

    def __getstate__(self):
        return self.__getstate__


if __name__=='__main__':
   root = tk.Tk()

   start(root).pack()
   root.mainloop()

所以这个应用程序的目标是一旦我点击了plus按钮,屏幕上就会有一个越来越多的数字。如果我保存它,关闭窗口,重新打开它,点击加载按钮,我将看到最后一次增加到的数字。我对pickle非常陌生,当前的代码会将这一点反馈给我:

1
2
3
4
5
6
    Exception in Tkinter callback
Traceback (most recent call last):
File"/Library/Frameworks/Python.framework/Versions/3.5/lib/python3.5/tkinter/__init__.py", line 1550, in __call__return self.func(*args)
File"/Users/caoanjie/pickleDemotry.py", line 18, in load
self.__dict__ = pickle.load(open("testtesttest.p","rb" ))pickle.
UnpicklingError: state is not a dictionary

我想知道这里有什么问题。此外,我在网上看到了很多教程或示例代码,它们的功能如下:

1
2
with open('save_game.dat', 'wb') as f:
    player= pickle.load

with是什么意思?


您的问题可以简化为一个完全不使用tkinter的小类:

1
2
3
4
5
6
7
8
9
10
>>> class Foo:
...     def __getstate__(self):
...         print('getstate')
...         return self.__getstate__
...
>>> obj = pickle.loads(pickle.dumps(Foo().__getstate__))
getstate
Traceback (most recent call last):
  File"<stdin>", line 1, in <module>
_pickle.UnpicklingError: state is not a dictionary

您正在清除__getstate__实例方法,而不是start类的完整状态。Python允许您这样做,假设您还实现了一个__setstate__方法,该方法知道如何从该信息中重建对象。从文档中:

Upon unpickling, if the class defines __setstate__(), it is called with the unpickled state. In that case, there is no requirement for the state object to be a dictionary. Otherwise, the pickled state must be a dictionary and its items are assigned to the new instance’s dictionary.

当您取消选择时,pickle会创建一个新的state实例,但是由于类没有__setstate__方法,pickle会尝试恢复对象的__dict__。这失败了,因为未选取的对象是一个实例方法,而不是一个dict。这表明你的方法有一个更大的问题。

pickle重新创建了整个对象,它不会恢复到现有对象中。在您的情况下,如果您腌制了整个start对象,它将恢复除您自己创建的对象之外的第二个start对象。您可以将该对象的__dict__分配给您的__dict__,但这是一个非常危险的建议。您将释放框架对象的整个状态,以支持您所处理的对象中发生的事情。无论如何,它可能无法处理整个对象,因为tkinter是一个C扩展模块。

相反,您应该将要保存和恢复的数据与恰好用于与用户交互的tkinter对象分开。这是一个常见的编程规则:将数据与表示分离。这里,我有一个保存数据的类,我可以将它与Tkinter类分开保存和恢复。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import tkinter as tk
import pickle

class State:
    def __init__(self):
        self.val = 0


class start(tk.Frame):
    def __init__(self,*args,**kwargs):
        tk.Frame.__init__(self,*args,**kwargs)
        frame = tk.Frame(self,width=600,height=600)
        self.state = State()
        self.plusButton = tk.Button(self,text="plus",command=self.plus)
        self.plusButton.pack()
        self.valLabel = tk.Label(self)
        self.valLabel.pack()
        self.saveButton = tk.Button(self,text="save",command=self.save)
        self.saveButton.pack()
        self.loadButton = tk.Button(self,text="load",command=self.load)
        self.loadButton.pack()
    def load(self):
        self.state = pickle.load(open("testtesttest.p","rb" ))
        self.valLabel.config(text="%d"%(self.state.val))
    def plus(self):
        self.state.val += 1
        self.valLabel.config(text="%d"%(self.state.val))
    def save(self):
        pickle.dump(self.state, open("testtesttest.p","wb" ), 4)

if __name__=='__main__':
   root = tk.Tk()

   start(root).pack()
   root.mainloop()