88

I want my class to implement Save and Load functions which simply do a pickle of the class. But apparently you cannot use 'self' in the fashion below. How can you do this?

self = cPickle.load(f)

cPickle.dump(self,f,2)
Delgan
  • 18,571
  • 11
  • 90
  • 141

5 Answers5

51

This is what I ended up doing. Updating the __dict__ means we keep any new member variables I add to the class and just update the ones that were there when the object was last pickle'd. It seems the simplest while maintaining the saving and loading code inside the class itself so calling code just does an object.save().

def load(self):
    f = open(self.filename, 'rb')
    tmp_dict = cPickle.load(f)
    f.close()          

    self.__dict__.update(tmp_dict) 


def save(self):
    f = open(self.filename, 'wb')
    cPickle.dump(self.__dict__, f, 2)
    f.close()
Community
  • 1
  • 1
  • 5
    A minor PEP8 nitpick - the functions should be named`load` and `save` in lowercase https://www.python.org/dev/peps/pep-0008/#function-names – Adam Matan Jun 17 '15 at 16:18
  • This is a `merge`. You should call `self.__dict__.clear()` before calling `self.__dict__.update(..)` – deepelement Aug 15 '17 at 22:00
  • 1
    Is there any reason in particular you aren't using "with" statements? – Disgusting Oct 04 '19 at 15:24
  • 2
    Why do you use the 3rd parameter =2? – J Agustin Barrachina Feb 18 '20 at 16:06
  • This solution has some nice properties, but one major con: you need to construct an object before calling the load function, and you may not have an option to call the constructor with valid data. So creating a factory classmethod makes more sense. – emem Jun 08 '20 at 08:34
  • What does the 3rd parameter =2 mean? – K. Symbol Jun 25 '22 at 03:08
  • @K.Symbol It determines the protocol, you can read more [here](https://stackoverflow.com/questions/23582489/python-pickle-protocol-choice). – Noa Sep 12 '22 at 09:27
30

The dump part should work as you suggested. for the loading part, you can define a @classmethod that loads an instance from a given file and returns it.

@classmethod
def loader(cls,f):
    return cPickle.load(f)

then the caller would do something like:

class_instance = ClassName.loader(f)
Ofri Raviv
  • 24,375
  • 3
  • 55
  • 55
  • So what you are saying is with class Foo, instance foo I can do foo.Save() but I cannot do foo.Load() I would have to do foo = foo.Load() -- (class methods can be called with an instance or class name) –  Apr 25 '10 at 20:39
  • 7
    It makes perfect sense that you should use foo=Foo.load(), and not foo=Foo();foo.load(). for example, if Foo has some variables that MUST be passed in to the init, you would need to make them up for foo=Foo(); or if the init does some heavy calculations of variables stored in the instance, it would be for nothing. – Ofri Raviv Apr 25 '10 at 20:52
  • 1
    This works, originally I had foo = Foo('somefilename') And Foo was doing its own loading of its data. Now I do a: foo = Foo.Load('somefilename') If I modify the definition of Foo, I can still use the new functions/members in the pickle loaded foo instance. –  Apr 25 '10 at 21:03
9

If you want to have your class update itself from a saved pickle… you pretty much have to use __dict__.update, as you have in your own answer. It's kind of like a cat chasing it's tail, however… as you are asking the instance to essentially "reset" itself with prior state.

There's a slight tweak to your answer. You can actually pickle self.

>>> import dill
>>> class Thing(object):
...   def save(self):
...     return dill.dumps(self)
...   def load(self, obj):
...     self.__dict__.update(dill.loads(obj).__dict__)
... 
>>> t = Thing()
>>> t.x = 1
>>> _t = t.save()
>>> t.x = 2
>>> t.x
2
>>> t.load(_t)
>>> t.x
1

I used loads and dumps instead of load and dump because I wanted the pickle to save to a string. Using load and dump to a file also works. And, actually, I can use dill to pickle an class instance to a file, for later use… even if the class is defined interactively. Continuing from above...

>>> with open('self.pik', 'w') as f:
...   dill.dump(t, f)
... 
>>> 

then stopping and restarting...

Python 2.7.10 (default, May 25 2015, 13:16:30) 
[GCC 4.2.1 Compatible Apple LLVM 5.1 (clang-503.0.40)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import dill
>>> with open('self.pik', 'r') as f:
...   t = dill.load(f)
... 
>>> t.x
1
>>> print dill.source.getsource(t.__class__)
class Thing(object):
  def save(self):
    return dill.dumps(self)
  def load(self, obj):
    self.__dict__.update(dill.loads(obj).__dict__)

>>> 

I'm using dill, which is available here: https://github.com/uqfoundation

Mike McKerns
  • 33,715
  • 8
  • 119
  • 139
6

This is how I did it. The advantage is that you do not need to create a new object. You can just load it directly.

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

@classmethod
def load(cls, filename):
    with open(filename, 'rb') as f:
        return pickle.load(f)

How to use it:

class_object.save()
filename = class_object.filename
del class_object

class_object = ClassName.load(filename)

Bellow, I updated the answer with a fully working minimal example. This can be adapted to your own needs.

import pickle

class Test:

    def __init__(self, something, filename) -> None:
        self.something = something
        self.filename = filename


    def do_something(self) -> None:
        print(id(self))
        print(self.something)

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


    @classmethod
    def load(cls, filename):
        with open(filename, 'rb') as f:
            return pickle.load(f)


test_object = Test(44, "test.pkl")
test_object.do_something()
test_object.save()
filename = test_object.filename
del test_object

recovered_object = Test.load(filename)
recovered_object.do_something()
Sorin Dragan
  • 510
  • 4
  • 9
2

There is an example of how to pickle an instance here, in the docs. (Search down for the "TextReader" example). The idea is to define __getstate__ and __setstate__ methods, which allow you to define what data needs to be pickled, and how to use that data to re-instantiate the object.

unutbu
  • 842,883
  • 184
  • 1,785
  • 1,677
  • 1
    This doesn't solve the problem because you still cannot inside the class's Load function call self = cPickle.load(f) in order to fill the class with the loaded data. Or course I can pickle the class data itself, but I'm trying to avoid writing all that code and being forced to update it when the class member variables change. –  Apr 25 '10 at 20:44