是否有一种简单的方法来挑选python函数(或以其他方式序列化其代码)?

时间:2009-08-10 07:25:47

标签: python function pickle

我正在尝试通过网络连接传输功能(使用asyncore)。是否有一种简单的方法来序列化一个python函数(在这种情况下,至少,没有副作用)这样的传输?

我希望有一对类似的功能:

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

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

12 个答案:

答案 0 :(得分:113)

您可以序列化函数字节码,然后在调用者上重构它。 marshal模块可用于序列化代码对象,然后可以将其重新组合成函数。即:

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

然后在远程进程中(在传输code_string之后):

import marshal, types

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

func(10)  # gives 100

一些警告:

  • marshal的格式(任何python字节码)在主要python版本之间可能无法兼容。

  • 仅适用于cpython实施。

  • 如果函数引用了您需要拾取的全局变量(包括导入的模块,其他函数等),您还需要对它们进行序列化,或者在远程端重新创建它们。我的例子只是给它了远程进程的全局命名空间。

  • 您可能需要做更多工作来支持更复杂的情况,例如闭包或生成器功能。

答案 1 :(得分:36)

查看Dill,它扩展了Python的pickle库,以支持更多种类型,包括函数:

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

它还支持对函数闭包中对象的引用:

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

答案 2 :(得分:14)

答案 3 :(得分:10)

最简单的方法可能是inspect.getsource(object)(参见inspect module),它返回一个带有函数或方法源代码的String。

答案 4 :(得分:6)

这完全取决于您是否在运行时生成函数:

如果你这样做 - inspect.getsource(object)将无法用于动态生成的函数,因为它从.py文件获取对象的源,因此只有在执行之前定义的函数才能作为源检索。

如果您的函数无论如何都放在文件中,为什么不让接收者访问它们,只传递模块和函数名称。

我能想到的动态创建函数的唯一解决方案是在传输之前将函数构造为字符串,传输源,然后在接收端将其eval()构建。

编辑:marshal解决方案看起来也非常聪明,不知道你可以序列化其他内置插件

答案 5 :(得分:5)

cloud包(pip install cloud)可以挑选任意代码,包括依赖项。请参阅https://stackoverflow.com/a/16891169/1264797

答案 6 :(得分:1)

此模块使用的基本功能涵盖了您的查询,并且您可以通过线路获得最佳压缩;看到有用的源代码:

y_serial.py module ::使用SQLite的仓库Python对象

“序列化+持久性::在几行代码中,将Python对象压缩并注释为SQLite;然后通过关键字按时间顺序检索它们,而不使用任何SQL。最有用的”标准“模块,用于存储无模式数据的数据库。“

http://yserial.sourceforge.net

答案 7 :(得分:1)

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

obj = pickle.dumps(code_string)

现在

exec(pickle.loads(obj))

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

答案 8 :(得分:1)

Cloudpickle可能正是您想要的。 Cloudpickle描述如下:

  

cloudpickle在使用Python的集群计算中特别有用   代码通过网络传送,可以在远程主机上执行   靠近数据。

用法示例:

def add_one(n):
  return n + 1

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

答案 9 :(得分:0)

您可以这样做:

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

现在,transmit(fn_generator())将发送fn(x,y)的实际定义,而不是对模块名称的引用。

您可以使用相同的技巧通过网络发送课程。

答案 10 :(得分:0)

这是一个帮助器类,您可以使用该类来包装功能以使它们可腌制。已经为marshal提到的警告将适用,但会尽可能使用咸菜。不会在序列化过程中保留全局或闭包。

    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)

答案 11 :(得分:0)

在现代Python中,您可以腌制函数以及许多变体。考虑一下

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

您可以腌制它

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

您可以腌制瓶盖

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

即使闭包使用局部变量

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

但是如果使用内部函数将其关闭,它将失败

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

有错误

pickle.PicklingError:无法腌制:找不到它作为__ main __。mypartial

经过Python 2.7和3.6测试