有没有一种简单的方法来腌制一个 python 函数(或者序列化它的代码)?

Posted

技术标签:

【中文标题】有没有一种简单的方法来腌制一个 python 函数(或者序列化它的代码)?【英文标题】:Is there an easy way to pickle a python function (or otherwise serialize its code)? 【发布时间】:2010-11-18 05:16:41 【问题描述】:

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

【问题讨论】:

这会比REST的所有序列化和API类都酷 【参考方案1】:

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

import marshal
def foo(x): return x*x
code_string = marshal.dumps(foo.__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 实现。

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

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

【讨论】:

在 Python 2.5 中,“新”模块已被弃用。 'new.function' 应该替换为 'types.FunctionType',在“导入类型”之后,我相信。 谢谢。这正是我一直在寻找的。根据一些粗略的测试,它可以像生成器一样工作。 如果您阅读 marshal 模块的前几段,您会发现它强烈建议使用 pickle 代替?泡菜页面也是如此。 docs.python.org/2/library/marshal.html 我正在尝试应用marshal 模块来序列化初始化为defaultdict(lambda : defaultdict(int)) 的字典字典。但它返回错误ValueError: unmarshallable object。注意我使用的是python2.7。任何想法?谢谢 在 Python 3.5.3 上,foo.func_code 引发 AttributeError。有没有其他方法获取功能码?【参考方案2】:

在现代 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: Can't pickle : it's not found as __ main __.mypartial

使用 Python 2.7 和 3.6 测试

【讨论】:

【参考方案3】:

这是一个帮助类,您可以使用它来包装函数以使它们可腌制。已经提到的marshal 的警告将适用,但尽可能使用pickle。没有努力在序列化过程中保留全局变量或闭包。

    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)

【讨论】:

【参考方案4】:

你可以这样做:

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

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

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

【讨论】:

【参考方案5】:

Cloudpickle 可能是您正在寻找的。 Cloudpickle描述如下:

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

使用示例:

def add_one(n):
  return n + 1

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

【讨论】:

【参考方案6】:
code_string = '''
定义 foo(x):
    返回 x * 2
定义栏(x):
    返回 x ** 2
'''

obj = pickle.dumps(code_string)

现在

执行(pickle.loads(obj)) 富(1) > 2 酒吧(3) > 9

【讨论】:

【参考方案7】:

查看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

【讨论】:

dill 在从函数和 lambda 获取源代码并将其保存到磁盘方面也做得很好,如果您更喜欢对象酸洗的话。 刚刚好。还有一个插入式解决方案,它在导入后直接工作,无需修改任何其他围绕 pickle 的代码。 它还保存了函数中的全局变量!【参考方案8】:

cloud 包(pip install cloud)可以腌制任意代码,包括依赖项。见https://***.com/a/16891169/1264797。

【讨论】:

【参考方案9】:

Pyro 能够do this for you。

【讨论】:

我需要坚持使用这个特定项目的标准库。 但这并不意味着您不能查看 Pyro 的代码来了解它是如何完成的 :) @AaronDigulla- 是的,但值得一提的是,在阅读他人发布的一行代码之前,您应该始终检查软件的许可证。在许多情况下,阅读他人的代码并重复使用这些想法而不引用源代码或遵守许可/复制限制可能会被视为抄袭和/或侵犯版权。【参考方案10】:

此模块使用的基本功能涵盖了您的查询,此外您还可以通过网络获得最佳压缩;请参阅指导性源代码:

y_serial.py 模块 :: 使用 SQLite 存储 Python 对象

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

http://yserial.sourceforge.net

【讨论】:

【参考方案11】:

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

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

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

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

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

【讨论】:

【参考方案12】:

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

【讨论】:

这个看起来不错,只是函数名是在代码中显式定义的,有点问题。我可以去掉代码的第一行,但可以通过执行“def \/n func():”之类的操作来破坏。我可以用函数本身腌制函数的名称,但我不能保证名称不会发生冲突,或者我必须将函数放在包装器中,这仍然不是最干净的解决方案,但是它可能必须这样做。 请注意,inspect 模块实际上只是询问函数的定义位置,然后从源代码文件中读取这些行——这并不复杂。 您可以使用函数的 .__name__ 属性找出函数的名称。你可以在 ^def\s*name\s*( 上做一个正则表达式替换,并给它你喜欢的任何名字。这不是万无一失的,但它适用于大多数事情。

以上是关于有没有一种简单的方法来腌制一个 python 函数(或者序列化它的代码)?的主要内容,如果未能解决你的问题,请参考以下文章

如何腌制“记忆化”的 Python 函数?

腌制数据--python(pickle标准库)

有没有一种简单的方法来检查一个对象是不是在 python 中是 JSON 可序列化的?

复制的套接字没有被腌制

有没有一种简单的方法来判断等待 Python GIL 花费了多少时间?

Python多处理-TypeError:无法腌制'_tkinter.tkapp'对象