Ben Randall Shaw
Ben Randall Shaw

Reputation: 49

Pickle will not work with tkinter

I'm making a little game with Tkinter, and it has a save function using pickle. However, when I try to save, it throws up the following message;

Exception in Tkinter callback
Traceback (most recent call last):
  File "C:\Python34\lib\tkinter\__init__.py", line 1533, in __call__
    return self.func(*args)
  File "C:\Users\Benedict\Documents\Python\Migrant Simulator\MigSim 2016.10\migrant-stimulator.py", line 260, in save
    pickle.dump(self.game,file)
_pickle.PicklingError: Can't pickle <class 'tkapp'>: attribute lookup tkapp on __main__ failed

The thing is, the data I'm trying to pickle contains nothing Tkinter-related, so I don't understand why it says it is <class 'tkapp'>
Here is a summary of the relevant bits of code:

...
class Game(object):

    def __init__(self,name,nodes={},start=None,history=[]):
        self.name=name
        self.nodes=nodes
        self.start=start
        self.history=history

class App:

    def __init__(self, master):
        self.master=master
...
    def save(self):
        if self.file_name==None:
            self.save_as()
        file=open(self.file_name,'wb')
        pickle.dump(self.game,file) # self.game is an instance of the Game class defined elsewhere
        print(str(type(self.game)))
        file.close()

    def save_as(self):
        self.file_name=filedialog.asksaveasfilename()
        self.save()
...
root = Tk()

app = App(root)

root.mainloop()

How can I fix this? I've tried changing __getstate__ as suggested in a related question, but it didn't work.

EDIT: Never mind, it turns out that deep in my data structure, I had left a BooleanVar.

Upvotes: 4

Views: 5465

Answers (2)

Franc Drobnič
Franc Drobnič

Reputation: 1085

In addition to what Brian Oakley suggested, it is also possible to save data as dill export:

def save(self):
    data = {"name": self.name,
            "nodes": self.nodes,
            ...
           }
    with open('data.pkl', 'wb') as f:
        dill.dump(data, f)

def load(self):
    with open('data.pkl', 'rb') as f:
        data = dill.load(f)
    self.name = data["name"]
    self.nodes = data["nodes"]
    ...

Note that here it is necessary to specify binary mode at opening the file.

Upvotes: 0

Bryan Oakley
Bryan Oakley

Reputation: 386315

The short answer is, you can't pickle anything tkinter related. The reason is that tkinter applications use an embedded Tcl interpreter which maintains the state of the GUI in memory, and Tcl doesn't know anything about the python pickle format (and likewise, pickle knows nothing about the tcl interpreter). There's simply no way to save and restore the data managed by the Tcl interpreter.

You will have to convert the information you want to save into some type of data structure, and then save and load this data structure.

For example

def save(self):
    data = {"name": self.name,
            "nodes": self.nodes,
            ...
           }
    with open('data.json', 'w') as f:
        json.dump(data, f)

def load(self):
    with open('data.json') as f:
        data = json.load(f)
    self.name = data["name"]
    self.nodes = data["nodes"]
    ...

If any of the values you want to store contain references to tkinter objects (eg: widgets, list of canvas item ids, etc.), you'll have to convert them to something else, and restore them at startup.

Upvotes: 3

Related Questions