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


100

我正在尝试通过网络连接(使用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()

Answers:


120

您可以序列化函数字节码,然后在调用方上对其进行重构。所述编组模块可以用于串行化处理的代码对象,然后可将其重新组装成一个函数。即:

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

一些警告:

  • 元帅的格式(与此有关的任何python字节码)在主要python版本之间可能不兼容。

  • 仅适用于cpython实现。

  • 如果该函数引用了您需要使用的全局变量(包括导入的模块,其他函数等),则也需要对它们进行序列化或在远程端重新创建它们。我的示例只是为它提供了远程进程的全局名称空间。

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


1
在Python 2.5中,不建议使用“新”模块。我相信,在“导入类型”之后,应将“ new.function”替换为“ types.FunctionType”。
Eric O Lebigot 09年

2
谢谢。这正是我想要的。基于一些粗略的测试,它对于生成器仍然有效。
Michael Fairley,2009年

2
如果您阅读了元帅模块的前几段,那么您会强烈建议使用泡菜代替吗?泡菜页面也一样。docs.python.org/2/library/marshal.html
dgorissen

1
我正在尝试将该marshal模块应用于序列化为的字典的字典defaultdict(lambda : defaultdict(int))。但是它返回错误ValueError: unmarshallable object。注意我是python2.7中的人。任何想法?谢谢
user17375 2013年

2
在Python 3.5.3上,foo.func_code引发AttributeError。还有另一种获取功能代码的方法吗?
AlQuemist

41

请查看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
dill在从函数和lambda中获取源代码并将其保存到磁盘方面也做得非常好,如果您更喜欢使用对象酸洗的话。
Mike McKerns 2014年

14

我需要坚持使用该特定项目的标准库。
迈克尔·费尔利

21
但这并不意味着您无法查看 Pyro的代码以了解其工作方式:)
Aaron Digulla 2009年

4
@ AaronDigulla-是的,但是值得一提的是,在阅读一行别人发布的代码之前,您应该始终检查该软件的许可证。在许多情况下,阅读别人的代码并重用思想而不引用来源或遵守许可/复制限制可能被视为窃和/或侵犯版权。
mdscruggs

12

最简单的方法可能是inspect.getsource(object)(请参阅inspect模块),该方法返回带有函数或方法的源代码的String。


看起来不错,除了在代码中显式定义了函数名之外,这有些问题。我可以剥离代码的第一行,但这可以通过执行类似'def \ / n func():'的操作来打破。我可以用函数本身来腌制函数的名称,但是我不能保证名称不会冲突,或者我必须将函数放入包装器中,这仍然不是最干净的解决方案,但是它可能必须这样做。
Michael Fairley,2009年

1
请注意,检查模块实际上只是询问函数的定义位置,然后从源代码文件中读取这些行-几乎不复杂。
太多的PHP

1
您可以使用.__ name__属性找出函数的名称。你可以做一个正则表达式替换^高清\ S * {名} \ S *(并给它的任何名称你喜欢它并非万无一失,但对大多数事情的工作。
太多的PHP

6

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

如果这样做- inspect.getsource(object)因为动态生成的函数会从.py文件中获取对象的源代码,因此不适用于动态生成的函数,因此只能将在执行之前定义的函数作为源来检索。

而且,如果您的函数仍然放置在文件中,为什么不让接收者访问它们,而只传递模块和函数名。

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

编辑:该marshal解决方案看起来也很聪明,不知道您可以序列化其他内置的东西




2

你可以这样做:

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

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

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


1

该模块使用的基本功能涵盖了您的查询,此外,您还可以通过网络获得最佳的压缩效果;参见说明性源代码:

y_serial.py模块::使用SQLite仓库Python对象

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

http://yserial.sourceforge.net


1

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

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

用法示例:

def add_one(n):
  return n + 1

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

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)
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.