[Zope] Bound methods, acquisition, __init__, Unpickleable object

Casey Duncan cduncan@kaivo.com
Mon, 09 Jul 2001 08:40:34 -0600


Bo Granlund wrote:
> 
> Hello zope users,
> 
> I have this little problem with bound methods and __init__. I have
> written something like:
> 
> class Root(Acquisition.Implicit, Persistent):
>         def __init__(self):
>                 self.container = []
>         def register_func(self, func):
>                 self.container.append(func)
> 
> class Spam(Acquisition.Implicit, Persistent):
>         def foo(self): pass
>         def __init__(self):
>                 # This fails at Root.register_func, in self.container.append
>                 self.register_func(self.foo)
>         def add_bound_method(self):
>                 # Works like charm, when called explicitly
>                 self.register_func(self.foo)
> 
> This results in UnpickleableError, 'Cannot pickle objects' when
> adding the product. Now if I call register_func from somewhere
> else than __init__ or manage_afterAdd, there is no problem, and
> everything works as I expect it to work. This is propably
> explained somewhere in deep detail, and I have managed to miss
> that documentation. Any pointers to documentation or ideas on
> how to set bound methods to other objects would be appreciated.
> 
> Best regards,
> Bo Granlund
> 

The ZODB uses pickling to store values persistently. One rule of
pickling is that you cannot pickle methods or functions. By appending
the function itself to the list, you are causing this error.

Another problem (which explains why you don't always get this error) is
that the ZODB has no way of knowing that you have changed container when
you use the append function. So, it does not commit the change. This is
not a problem in __init__ because the whole instance is new and the ZODB
commits it all. If you change a mutable object in place (such as a list
or dictionary) you need to signal the ZODB to commit the change. To do
this, add this code after the change it made:

self._p_changed = 1

Attributes starting with "_p_" are reserved for the persistance
machinery. Changes to immutable objects (strings, ints, etc) do not need
this signalling.

Now to get around the problem of storing functions, I would suggest
storing the name of the function (a string) in the list instead of the
function itself. The use getattr to retreive it when it is called. Here
is your code revised to do this:

class Root(Acquisition.Implicit, Persistent):
        def __init__(self):
                self.container = []
        def register_func(self, func_name):
                self.container.append(func_name)
		self._p_changed = 1
	def call_func(self, func_index):
                getattr(self.aq_base, self.container[func_index])()

class Spam(Acquisition.Implicit, Persistent):
        def foo(self): pass
        def __init__(self):
                # This fails at Root.register_func, in
self.container.append
                self.register_func('foo')
        def add_bound_method(self):
                # Works like charm, when called explicitly
                self.register_func('foo')

The self.aq_base part in call_func will suppress acquisition making sure
it is a real method of the object and not an acquired one.

hth,
-- 
| Casey Duncan
| Kaivo, Inc.
| cduncan@kaivo.com
`------------------>