Dynamic traits do not survive pickling

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.