2

I have a class (Bar) which effectively has its own state and callback(s) and is used by another class (Foo):

 class Foo(object):

     def __init__(self):
         self._bar = Bar(self.say, 10)
         self._bar.work()

     def say(self, msg):
         print msg

 class Bar(object):
     def __init__(self, callback, value):
         self._callback = callback
         self._value = value
         self._more = { 'foo' : 1, 'bar': 3, 'baz': 'fubar'}

     def work(self):
         # Do some work
         self._more['foo'] = 5
         self._value = 10
         self._callback('FooBarBaz')

 Foo()

Obviously I can't pickle the class Foo since Bar has an instancemethod, so I'm left with the following solution of implementing __getstate__ & __setstate__ in Bar to save self._value & self._more, but I have to instantiate the self._callback method as well (i.e. call __init__() from the outer class Foo passing the callback function.

But I cannot figure out how to achieve this.

Any help is much appreciated.
Thanks.

nbaztec
  • 402
  • 4
  • 13

1 Answers1

1

I think if you need to serialize something like this you need to be able to define your callback as a string. For example, you might say that callback = 'myproject.callbacks.foo_callback'.

Basically in __getstate__ you'd replace the _callback function with something you could use to look up the function later like self._callback.__name__.

In __setstate__ you'd replace _callback with a function.

This depends on your functions all having real names so you couldn't use a lambda as a callback and expect it to be serialized. You'd also need a reasonable mechanism for looking up your functions by name.

You could potentially use __import__ (something like: 'myproject.somemodule.somefunc' dotted name syntax could be supported that way, see http://code.google.com/p/mock/source/browse/mock.py#1076) or just define a lookup table in your code.

Just a quick (untested, sorry!) example assuming you have a small set of possible callbacks defined in a lookup table:

def a():
    pass

callbacks_to_name = {a: 'a'
                     # ...
                     }

callbacks_by_name = {'a': a,
             # ...
             }

class C:
    def __init__(self, cb):
        self._callback = cb

    def __getstate__(self):
        self._callback = callbacks_to_name[self._callback]
        return self.__dict__

    def __setstate__(self, state):
        state[_callback] = callbacks_by_name[self._callback]

I'm not sure what your use case is but I'd recommend doing this by serializing your work items to JSON or XML and writing a simple set of functions to serialize and deserialize them yourself.

The benefit is that the serialized format can be read and understood by humans and modified when you upgrade your software. Pickle is tempting because it seems close enough, but by the time you have a serious pile of __getstate__ and __setstate__ you haven't really saved yourself much effort or headache over building your own scheme specifically for your application.

stderr
  • 8,567
  • 1
  • 34
  • 50
  • You, Sir, have just given me a new direction. The functions are not lambdas, so certainly can have fully qualified names. Now testing the code... – nbaztec Aug 09 '12 at 13:44
  • Almost there, just need to figure out a way to get fully-qualified name of the function and then break 'em up to be used in `getattr()` – nbaztec Aug 09 '12 at 13:58
  • I am unable to get a FQN for the function like shown here [link](http://stackoverflow.com/questions/2020014/get-fully-qualified-class-name-of-an-object-in-python). It returns `__main__.instancemethod.functionname` instead of showing the class name. Any suggestions? – nbaztec Aug 09 '12 at 14:09
  • I'm afraid that until Python 3.3 and PEP-0395 (qualname) you're going to want to move everything out of your `__main__` module. http://www.python.org/dev/peps/pep-0395/#in-a-bit-of-a-pickle – stderr Aug 09 '12 at 18:47
  • Thanks @Mike, but all that while, I was fiddling with this and in fact managed to pickle/unpickle the method correctly, but it comes out as `unbound` (and I cannot bind a self to it at runtime) So I'm using custom methods to save the data & set them using another method _after_ `init()` – nbaztec Aug 10 '12 at 06:20