1

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

So the goal of this app is once I hit the plusbutton, there will be an increasing number on the screen. And if I save it, close the window, reopen it, and hit the load button, I will see the last time the number I increased to. I'm very new to pickle, and the current code gives this back to me:

    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

I wonder what the problem is here. Also, I see a lot of tutorials or sample code online that does things like:

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

What does with mean?

user
  • 5,370
  • 8
  • 47
  • 75
angieShroom
  • 133
  • 3
  • 9
  • [```with``` in the docs](https://docs.python.org/3/reference/compound_stmts.html#the-with-statement) - when used with a file object, it ensures that the file is closed even if an exception is raised. There are many references online, this one might help - http://effbot.org/zone/python-with-statement.htm. Here is an SO Q&A - http://stackoverflow.com/q/1369526/2823755 – wwii Nov 23 '16 at 16:23
  • Typically, you should always post the complete Traceback in your questions. – wwii Nov 23 '16 at 16:28
  • ```def __getstate__(self):``` looks problematic - what does it return if you call it *manually*? – wwii Nov 23 '16 at 16:33
  • While it might not directly solve your problem, this SO Q&A may be worth reading - http://stackoverflow.com/q/4529815/2823755. – wwii Nov 23 '16 at 16:41
  • You might notice when reading Python code here on SO and elsewhere that class names always start with a capital letter. That is one aspect of the Python style guide, https://www.python.org/dev/peps/pep-0008/, that seems universally implemented. – wwii Nov 23 '16 at 17:07

1 Answers1

6

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()
ali_m
  • 71,714
  • 23
  • 223
  • 298
tdelaney
  • 73,364
  • 6
  • 83
  • 116