【问题标题】:How to pickle a python function with its dependencies?如何腌制一个python函数及其依赖项?
【发布时间】:2012-04-20 08:11:59
【问题描述】:

作为对这个问题的跟进: Is there an easy way to pickle a python function (or otherwise serialize its code)?

我想从上面的帖子中看到这个项目符号的示例:

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

我有一个简单的测试,我正在使用 marshal 将函数字节码写入文件:

def g(self,blah): 
    print blah

def f(self):
    for i in range(1,5):
        print 'some function f'
        g('some string used by g')

data = marshal.dumps(f.func_code)

file = open('/tmp/f2.txt', 'w')
file.write(data)

然后我开始一个新的python实例:

file = open('/tmp/f2.txt', 'r')
code = marshal.loads(file.read())
func2 = types.FunctionType(code, globals(), "some_func_name");
func2('blah')

这会导致:

NameError: global name 'g' is not defined

这与我为包含 g 所做的不同方法无关。我已经尝试过基本相同的方法将 g 作为 f 发送,但 f 仍然看不到 g。如何让 g 进入全局命名空间,以便 f 在接收过程中使用它?

有人还建议将 pyro 视为如何执行此操作的示例。我已经尝试理解disco项目中的相关代码。我参加了他们的 dPickle 课程并尝试在独立应用程序中重新创建他们的 disco/tests/test_pickle.py 功能,但没有成功。我的实验在使用转储调用进行函数编组时遇到问题。无论如何,也许接下来是火焰兵探索。

总之,我所追求的基本功能是能够通过网络发送一个方法,并让所有基本的“工作区”方法随它一起发送(如 g)。

答案的变化示例:

工作函数_writer:

import marshal, types

def g(blah): 
    print blah


def f():
    for i in range(1,5):
        print 'some function f'
        g('blah string used by g')


f_data = marshal.dumps(f.func_code)
g_data = marshal.dumps(g.func_code);

f_file = open('/tmp/f.txt', 'w')
f_file.write(f_data)

g_file = open('/tmp/g.txt', 'w')
g_file.write(g_data)

工作函数_reader:

import marshal, types

f_file = open('/tmp/f.txt', 'r')
g_file = open('/tmp/g.txt', 'r')

f_code = marshal.loads(f_file.read())
g_code = marshal.loads(g_file.read())

f = types.FunctionType(f_code, globals(), 'f');
g = types.FunctionType(g_code, globals(), 'g');

f()

【问题讨论】:

    标签: python function pickle


    【解决方案1】:

    我已经尝试过与 f 发送 g 基本相同的方法,但 f 仍然看不到 g。如何让 g 进入全局命名空间,以便 f 在接收过程中使用它?

    将其分配给全局名称g。 (我看到您将f 分配给func2 而不是f。如果您正在使用g 做类似的事情,那么很清楚为什么f 找不到g。请记住名称解析发生在运行时 -- g 在您调用 f 之前不会被查找。)

    当然,我猜是因为您没有显示用于执行此操作的代码。

    最好创建一个单独的字典,用于您要解压的函数的全局命名空间——一个沙盒。这样一来,它们的所有全局变量都将与您正在执行此操作的模块分开。因此您可以执行以下操作:

    sandbox = {}
    
    with open("functions.pickle", "rb") as funcfile:
        while True:
            try:
                code = marshal.load(funcfile)
            except EOFError:
                 break
            sandbox[code.co_name] = types.FunctionType(code, sandbox, code.co_name)
    

    在此示例中,我假设您已将所有函数的代码对象一个接一个地放在一个文件中,并且在读取它们时,我会获取代码对象的名称并将其用作两者的基础函数对象的名称及其在沙盒字典中存储的名称。

    在 unpickled 函数中,沙盒字典是它们的 globals(),因此在 f() 中,g 的值来自 sandbox["g"]。调用f 则为:sandbox["f"]("blah")

    【讨论】:

    • 哦,哇,我没有意识到分配的参考有什么不同!谢谢!将发布工作代码。
    • 太好了,我喜欢沙盒。想要探索下一个自动序列化所有函数依赖项的功能。有点像迪斯科 modutil.find_modules 方法的作用。感谢您的帮助。
    【解决方案2】:

    每个模块都有自己的全局变量,没有通用的全局变量。我们可以将恢复的功能“植入”到某个模块中,并像使用普通模块一样使用它。

    -- 保存--

    import marshal
    def f(x):
        return x + 1
    def g(x):
        return f(x) ** 2
    funcfile = open("functions.pickle", "wb")
    marshal.dump(f.func_code, funcfile)
    marshal.dump(g.func_code, funcfile)
    funcfile.close()
    

    -- 恢复--

    import marshal
    import types
    open('sandbox.py', 'w').write('')  # create an empty module 'sandbox'
    import sandbox
    with open("functions.pickle", "rb") as funcfile:
        while True:
            try:
                code = marshal.load(funcfile)
            except EOFError:
                 break
            func = types.FunctionType(code, sandbox.__dict__, code.co_name)
            setattr(sandbox, code.co_name, func)   # or sandbox.f = ... if the name is fixed
    assert sandbox.g(3) == 16   # f(3) ** 2
    # it is possible import them from other modules
    from sandbox import g
    

    已编辑:
    您也可以导入一些模块。例如从外部“sys”到“sandbox”命名空间:

    sandbox.sys = __import__('sys')
    

    或相同:

    exec 'import sys' in sandbox.__dict__
    assert 'sys' in sandbox, 'Verify imported into sandbox'
    

    您的原始代码可以工作,如果您不是在 ipython 交互中而是在 python 程序或普通 python 交互中执行它!!!

    Ipython 使用了一些奇怪的命名空间,它不是 sys.modules 中任何模块的 dict。普通 python 或任何主程序使用sys.modules['__main__'].__dict__ 作为 globals()。任何模块都使用that_module.__dict__ 这也可以,只有 ipython 交互是一个问题。

    【讨论】:

    • 谢谢! +1 对此也很好奇。
    • @RyanR。如果使用普通 python 而不是 ipython,您的原始代码将可以工作。
    • 不是 'import x ; x.method()' 类型用例在远程脚本中有问题吗?如:stackoverflow.com/questions/10099326/…
    【解决方案3】:

    2020 年 9 月更新:请参阅下方 @ogrisel 的评论。在我于 2013 年写下这个答案的原始版本后不久,PiCloud 的开发人员就转移到了 Dropbox,尽管七年后很多人仍在使用 cloudpickle 模块。该模块进入了 Apache Spark,并在那里继续得到维护和改进。我正在相应地更新下面的示例和背景文本。

    云泡菜

    cloudpickle 包能够腌制函数、方法、类,甚至是 lambda,以及任何依赖项。要试用,只需pip install cloudpickle,然后:

    import cloudpickle
    
    def foo(x):
        return x*3
    
    def bar(z):
        return foo(z)+1
    
    x = cloudpickle.dumps(bar)
    del foo
    del bar
    
    import pickle
    
    f = pickle.loads(x)
    print(f(3))  # displays "10"
    
    

    换句话说,只需像使用pickle.* 一样调用cloudpickle.dump()cloudpickle.dumps(),然后使用本机pickle.load()pickle.loads() 解冻。

    背景

    PiCcloud.com 发布了 LGPL 下的 cloud python 包,其他开源项目很快开始使用它(google for cloudpickle.py 看到一些)。 picloud.com 的人们有动力将精力投入到通用代码酸洗工作上——他们的整个业务都是围绕它建立的。这个想法是,如果你有 cpu_intensive_function() 并想在 Amazon 的 EC2 网格上运行它,你只需替换:

    cpu_intensive_function(some, args) 
    

    与:

    cloud.call(cpu_intensive_function, some, args)
    

    后者使用cloudpickle 提取任何依赖的代码和数据,将其发送到EC2,运行它,并在您调用cloud.result() 时将结果返回给您。

    Picloud 以毫秒为单位计费,它非常便宜,而且我一直使用它进行蒙特卡罗模拟和金融时间序列分析,当时我需要数百个 CPU 内核,每个内核只需要几秒钟。多年过去了,我仍然不能说太多关于它的好话,我什至没有在那里工作。

    【讨论】:

    • 谢谢您,先生 :) 我已经与莳萝苦苦挣扎了几个小时,但云只是直接工作,我相信这应该是公认的答案
    • 由于原有的PiCloud客户端SDK不再维护,开始了一个新项目,只是为了维护cloudpickle功能:github.com/cloudpipe/cloudpickle:pip install cloudpickle
    • @stevegt :您的示例似乎没有正确保存内置函数:)
    • Cloudpickle 在 dill 无法正确重新导入依赖项的情况下工作,谢谢!
    • 当时我在几个大学项目中使用了 piCloud,效果很好。我知道仍然没有一个好的选择。
    【解决方案4】:

    您可以通过导入 __main__ 并使用该模块中可用的方法来更好地处理全局对象。这就是dill 所做的,以便在 python 中序列化几乎任何东西。基本上,当 dill 序列化一个交互式定义的函数时,它会在序列化和反序列化方面对__main__ 使用一些名称修饰,从而使__main__ 成为有效模块。

    >>> import dill
    >>> 
    >>> def bar(x):
    ...   return foo(x) + x
    ... 
    >>> def foo(x):
    ...   return x**2
    ... 
    >>> bar(3)
    12
    >>> 
    >>> _bar = dill.loads(dill.dumps(bar))
    >>> _bar(3)
    12
    

    实际上,dill 将它的类型注册到 pickle 注册表中,所以如果你有一些使用 pickle 的黑盒代码并且你不能真正编辑它,那么只需导入 dill 就可以神奇地使其工作而无需猴子补丁第三方代码。

    或者,如果您希望将整个解释器会话作为“python 图像”发送过来,dill 也可以这样做。

    >>> # continuing from above
    >>> dill.dump_session('foobar.pkl')
    >>>
    >>> ^D
    dude@sakurai>$ python
    Python 2.7.5 (default, Sep 30 2013, 20:15:49) 
    [GCC 4.2.1 (Apple Inc. build 5566)] on darwin
    Type "help", "copyright", "credits" or "license" for more information.
    >>> import dill
    >>> dill.load_session('foobar.pkl')
    >>> _bar(3)
    12
    

    您可以轻松地通过 ssh 将图像发送到另一台计算机,然后从您离开的地方开始,只要存在 pickle 的版本兼容性以及有关 python 更改和正在安装的东西的常见警告。

    【讨论】:

    • 但是如果一个 Python 程序将 foo 和 bar 和 pickles bar 定义到一个文件中(使用 dill),另一个 Python 程序将这个 pickled 文件加载到 _bar 并调用 _bar(3),它会出错foo 未定义。为什么在这种情况下它不起作用?
    • 我不确定我明白你到底在问什么,你能否提供更多细节(无论是在它自己的问题中,还是在dill 的 github 问题页面上)?跨度>
    • 我在这里开了一个新问题:github.com/uqfoundation/dill/issues/176
    【解决方案5】:

    当被腌制的函数与腌制一起在主模块中时,Dill(以及其他 pickle 变体、cloudpickle 等)似乎可以工作。如果您要从另一个模块中提取函数,则在取消提取时必须存在该模块名称。我似乎找不到解决此限制的方法。

    【讨论】:

    猜你喜欢
    • 2016-08-24
    • 2015-06-26
    • 2018-03-05
    • 2021-09-30
    • 2021-08-19
    • 1970-01-01
    • 2017-11-26
    • 2014-05-30
    • 1970-01-01
    相关资源
    最近更新 更多