Is there an easy way to pickle a python function (or otherwise serialize its code)?

PythonFunctionPickle

Python Problem Overview


I'm trying to transfer a function across a network connection (using asyncore). Is there an easy way to serialize a python function (one that, in this case at least, will have no side effects) for transfer like this?

I would ideally like to have a pair of functions similar to these:

def transmit(func):
    obj = pickle.dumps(func)
    [send obj across the network]

def receive():
    [receive obj from the network]
    func = pickle.loads(s)
    func()

Python Solutions


Solution 1 - Python

You could serialise the function bytecode and then reconstruct it on the caller. The marshal module can be used to serialise code objects, which can then be reassembled into a function. ie:

import marshal
def foo(x): return x*x
code_string = marshal.dumps(foo.__code__)

Then in the remote process (after transferring code_string):

import marshal, types

code = marshal.loads(code_string)
func = types.FunctionType(code, globals(), "some_func_name")

func(10)  # gives 100

A few caveats:

  • marshal's format (any python bytecode for that matter) may not be compatable between major python versions.

  • Will only work for cpython implementation.

  • If the function references globals (including imported modules, other functions etc) that you need to pick up, you'll need to serialise these too, or recreate them on the remote side. My example just gives it the remote process's global namespace.

  • You'll probably need to do a bit more to support more complex cases, like closures or generator functions.

Solution 2 - Python

Check out Dill, which extends Python's pickle library to support a greater variety of types, including functions:

>>> import dill as pickle
>>> def f(x): return x + 1
...
>>> g = pickle.dumps(f)
>>> f(1)
2
>>> pickle.loads(g)(1)
2

It also supports references to objects in the function's closure:

>>> def plusTwo(x): return f(f(x))
...
>>> pickle.loads(pickle.dumps(plusTwo))(1)
3

Solution 3 - Python

Pyro is able to do this for you.

Solution 4 - Python

The most simple way is probably inspect.getsource(object) (see the inspect module) which returns a String with the source code for a function or a method.

Solution 5 - Python

It all depends on whether you generate the function at runtime or not:

If you do - inspect.getsource(object) won't work for dynamically generated functions as it gets object's source from .py file, so only functions defined before execution can be retrieved as source.

And if your functions are placed in files anyway, why not give receiver access to them and only pass around module and function names.

The only solution for dynamically created functions that I can think of is to construct function as a string before transmission, transmit source, and then eval() it on the receiver side.

Edit: the marshal solution looks also pretty smart, didn't know you can serialize something other thatn built-ins

Solution 6 - Python

In modern Python you can pickle functions, and many variants. Consider this

import pickle, time
def foobar(a,b):
    print("%r %r"%(a,b))

you can pickle it

p = pickle.dumps(foobar)
q = pickle.loads(p)
q(2,3)

you can pickle closures

import functools
foobar_closed = functools.partial(foobar,'locked')
p = pickle.dumps(foobar_closed)
q = pickle.loads(p)
q(2)

even if the closure uses a local variable

def closer():
    z = time.time()
    return functools.partial(foobar,z)
p = pickle.dumps(closer())
q = pickle.loads(p)
q(2)

but if you close it using an internal function, it will fail

def builder():
    z = 'internal'
    def mypartial(b):
        return foobar(z,b)
    return mypartial
p = pickle.dumps(builder())
q = pickle.loads(p)
q(2)

with error

> pickle.PicklingError: Can't pickle : it's not found as __ main __.mypartial

Tested with Python 2.7 and 3.6

Solution 7 - Python

The cloud package (pip install cloud) can pickle arbitrary code, including dependencies. See https://stackoverflow.com/a/16891169/1264797.

Solution 8 - Python

code_string = '''
def foo(x):
return x * 2
def bar(x):
return x ** 2
'''

obj = pickle.dumps(code_string)

Now

exec(pickle.loads(obj))

foo(1) > 2 bar(3) > 9

Solution 9 - Python

Cloudpickle is probably what you are looking for. Cloudpickle is described as follows: > cloudpickle is especially useful for cluster computing where Python > code is shipped over the network to execute on remote hosts, possibly > close to the data.

Usage example:

def add_one(n):
  return n + 1

pickled_function = cloudpickle.dumps(add_one)
pickle.loads(pickled_function)(42)

Solution 10 - Python

You can do this:

def fn_generator():
    def fn(x, y):
        return x + y
    return fn

Now, transmit(fn_generator()) will send the actual definiton of fn(x,y) instead of a reference to the module name.

You can use the same trick to send classes across network.

Solution 11 - Python

The basic functions used for this module covers your query, plus you get the best compression over the wire; see the instructive source code:

y_serial.py module :: warehouse Python objects with SQLite

"Serialization + persistance :: in a few lines of code, compress and annotate Python objects into SQLite; then later retrieve them chronologically by keywords without any SQL. Most useful "standard" module for a database to store schema-less data."

http://yserial.sourceforge.net

Solution 12 - Python

Here is a helper class you can use to wrap functions in order to make them picklable. Caveats already mentioned for marshal will apply but an effort is made to use pickle whenever possible. No effort is made to preserve globals or closures across serialization.

    class PicklableFunction:
        def __init__(self, fun):
            self._fun = fun
    
        def __call__(self, *args, **kwargs):
            return self._fun(*args, **kwargs)
    
        def __getstate__(self):
            try:
                return pickle.dumps(self._fun)
            except Exception:
                return marshal.dumps((self._fun.__code__, self._fun.__name__))
    
        def __setstate__(self, state):
            try:
                self._fun = pickle.loads(state)
            except Exception:
                code, name = marshal.loads(state)
                self._fun = types.FunctionType(code, {}, name)

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionMichael FairleyView Question on Stackoverflow
Solution 1 - PythonBrianView Answer on Stackoverflow
Solution 2 - PythonJosh RosenView Answer on Stackoverflow
Solution 3 - PythonRichieHindleView Answer on Stackoverflow
Solution 4 - PythonAaron DigullaView Answer on Stackoverflow
Solution 5 - PythonkurczakView Answer on Stackoverflow
Solution 6 - Pythonam70View Answer on Stackoverflow
Solution 7 - PythonstevegtView Answer on Stackoverflow
Solution 8 - PythonYanni PapadakisView Answer on Stackoverflow
Solution 9 - PythonSimon CView Answer on Stackoverflow
Solution 10 - PythonFardin AbdiView Answer on Stackoverflow
Solution 11 - Pythoncode43View Answer on Stackoverflow
Solution 12 - PythonmemeplexView Answer on Stackoverflow