Skip to content Skip to sidebar Skip to footer

Use Pickle To Load A State For Class

I'm trying to get my feet wet with pickle, so I write a little sample code like this: class start(tk.Frame): def __init__(self,*args,**kwargs): tk.Frame.__init__(self,*

Solution 1:

Your problem can be simplified to a small class that doesn't use tkinter at all:

>>> 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

You are pickling the __getstate__ instance method, not the full state of the start class. Python lets you do that, assuming that you also implement a __setstate__ method that knows how to rebuild an object from that information. From the docs:

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.

When you unpickle, pickle creates a new instance of state but since the class has no __setstate__ method, pickle tries to restore the object's __dict__. That fails because the unpickled object is an instance method, not a dict. And this shows a bigger problem with your approach.

pickle recreates entire objects, it doesn't restore into existing objects. In your case, if you pickled the entire start object, it would restore a second start object in addition to the one you created yourself. You could assign that object's __dict__ to your __dict__, but that is a very risky proposition. You would loose the entire state of your Frame object in favor of what happened to be in the object you pickled. Its likely impossible to pickle the entire object anyway because tkinter is a C extension module.

Instead, you should separate the data you want to save and restore from the tkinter object you happen to use to interact with the user. This is a common programming rule: separate data from presentation. Here, I have a class holding data and I can save and restore it separate from the tkinter class.

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()

Post a Comment for "Use Pickle To Load A State For Class"