# Pickling

One of the main features of Stackless is its ability to pickle and unpickle tasklets. That means that a running program inside a tasklet can be persistently stored to a file or string. Later, it can be restored again and can continue to run at the point where it was previously halted. This need not be on the same machine!

Example - Pickling and unpickling simple tasklets:

from stackless import run, schedule, tasklet
import pickle

def aCallable(name):
print "  aCallable<%s>: Before schedule()" % name
schedule()
print "  aCallable<%s>: After schedule()" % name

for name in "ABCDE":

print "Schedule 1:"
schedule()

print
print "Pickling..."

print
print "Schedule 2:"
schedule()

print
schedule()


## Custom Tasklet or Channel Subclasses

Subclassing the tasklet or channel classes is pretty straightforward. In fact, if you need to persist any extra information with your tasklets or channels, you will need to do this. Neither of the classes have their slots or instance dictionary persisted with them when pickled.

The following example should illustrate exactly what the problem is.

class CustomTasklet(stackless.tasklet):
name = "UNNAMED"

def __str__(self):

def f(): pass

t1.name = "Pointless"

s = pickle.dumps(t1)


Output:

>>> print t2.name == t1.name
False
>>> print t2.name
'UNNAMED'


However, you can easily get around this by overriding __reduce__() / __reduce_ex__() and __setstate__() to add whatever else you want to be persisted.

Example - Named tasklets that persist their name:

class CustomTasklet(stackless.tasklet):
name = "UNNAMED"

def __str__(self):

# When this is present, it is called in lieu of __reduce__.
# As the base tasklet class provides it, we need to as well.
def __reduce_ex__(self, pickleVersion):
return self.__reduce__()

def __reduce__(self):
# Get into the list that will eventually be returned to
# __setstate__ and append our own entry into it (the
# dictionary of instance variables).
l = list(ret[2])
l.append(self.__dict__)
ret[2] = tuple(l)
return tuple(ret)

def __setstate__(self, l):
# Update the instance dictionary with the value we added in.
self.__dict__.update(l[-1])
# Let the tasklet get on with being reconstituted by giving
# it the original list (removing our addition).

def f(): pass

t1.name = "Pointless"

s = pickle.dumps(t1)


Output:

>>> print t2.name == t1.name
True
>>> print t2.name
'Pointless'


Pickling a tasklet that is blocked on a channel, will not result in the pickling of that channel unless you are explicitly pickling a reference to that channel along with it.

If a tasklet is blocked on a channel that is not in any scope contained in the function the tasklet is bound to, then it will not be pickled.

Example - Channel not pickled:

def f():

c = stackless.channel()

s = pickle.dumps(t1)

# The tasklet will not be attached to a channel.
assert t2._channel is None


Example - Channel pickled:

def f(c):

c = stackless.channel()

s = pickle.dumps(t1)

# The tasklet will be attached to a channel.
assert t2._channel is not None


## Channels

Pickling a channel, will also pickle any tasklets currently blocked on it. But sometimes you just want to pickle the channel with only some of those tasklets still blocked on it, or perhaps, tasklets you isolate from the channel on their own. You can do this by using __reduce__() and __setstate__().

Example - Removing a tasklet from a channel and pickling the results:

# Given a channel 'c' with four tasklets blocked on it, where
# we want to just pickle the first.

# Get the channel state.
x, y, (balance, flags, tasklets) = c.__reduce__()

# Get the tasklet and remove it from the ones on the channel.

# Rebuild the channel without the tasklet.  You do not need to
# bother adjusting the balance for the changes you made to the
# list of blocked tasklets, as it is recalculated automatically.
# This will replace the channels existing state.  But if you
# want to keep the channel as it is, you can create a new
# and use it in place of 'c'.