Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Dynamic traits do not survive pickling

traits_pickle_problem.py

from traits.api import HasTraits, List
import cPickle

class Client(HasTraits):
   data = List

class Person(object):
   def __init__(self):
      self.client = Client()
      # dynamic handler
      self.client.on_trait_event(self.report,'data_items')

   def report(self,obj,name,old,new):
      print 'client added-- ' , new.added

if __name__ == '__main__':
   p = Person()
   p.client.data = [1,2,3]
   p.client.data.append(10)
   cPickle.dump(p,open('testTraits.pkl','wb'))

The above code reports a dynamic trait. Everything works as expected in this code. However, using a new python process and doing the following:

>>> from traits_pickle_problem import Person, Client                                              
>>> p=cPickle.load(open('testTraits.pkl','rb'))                                                   
>>> p.client.data.append(1000)  

causes no report of the list append. However, re-establishing the listener separately as follows:

>>> p.client.on_trait_event(p.report,'data_items')                                                
>>> p.client.data.append(1000)                                                                    
client added--  [1000]     

makes it work again.

Am I missing something or does the handler need to be re-established in __setstate__ during the unpickling process.

Any help appreciated. This is for Python 2.7 (32-bit) on windows with traits version 4.30.

like image 626
reckoner Avatar asked Apr 12 '14 16:04

reckoner


1 Answers

Running pickletools.dis(cPickle.dumps(p)), you can see the handler object being referenced:

  ...
  213: c        GLOBAL     'traits.trait_handlers TraitListObject'
  ...

But there's no further information on how it should be wired to the report method. So either the trait_handler doesn't pickle itself out properly, or it's an ephemeral thing like a file handle that can't be pickled in the first place.

In either case, your best option is to overload __setstate__ and re-wire the event handler when the object is re-created. It's not ideal, but at least everything is contained within the object.

class Person(object):
    def __init__(self):
        self.client = Client()
        # dynamic handler
        self.client.on_trait_event(self.report, 'data_items')

    def __setstate__(self, d):
        self.client = d['client']
        self.client.on_trait_event(self.report, 'data_items')

    def report(self, obj, name, old, new):
        print 'client added-- ', new.added

Unpickling the file now correctly registers the event handler:

p=cPickle.load(open('testTraits.pkl','rb'))
p.client.data.append(1000)
>>> client added--  [1000]

You might find this talk Alex Gaynor did at PyCon interesting. It goes into the high points of how pickling work under the hood.

EDIT - initial response used on_trait_change - a typo that appears to work. Changed it back to on_trait_event for clarity.

like image 123
dmcauslan Avatar answered Sep 28 '22 07:09

dmcauslan