更改腌制对象的功能

时间:2013-07-02 18:38:30

标签: python function python-2.7 function-pointers pickle

据我所知,pickle可以在文件和项目之间发送对象,只要两个名称空间中都存在这些对象。我有两个应用程序,它将传递一个Prime对象。

class Prime():
    def __init__(self):
        self.a = 1
    def func(self):
        print(self.a)

两个应用程序都以上述版本的prime开头。但是第一个应用程序将不得不改变Prime的功能,这样func就会打印出来(“hello world”)。然后第二个应用程序将通过pickle接收Prime的第一个版本并使用:

Second.py:

i = Prime
i.func()
with open("temp.txt", "r") as text:
    o = pickle.load(text)
    o.func()

输出:

1
hello world

我的2部分问题是这个。如果第二个应用程序只在其命名空间中具有Prime的原始版本,那么只要函数名称没有改变,它就可以使用第一个版本吗?如果是,我怎样才能改变第一个Prime.func的功能?

2 个答案:

答案 0 :(得分:1)

唯一的正确方法是让两个应用程序使用完全相同的版本,无论哪个模块定义Prime类,都可以在完全相同的限定名下导入。正如What can be pickled and unpickled?所解释的那样:

  

...函数(内置和用户定义)由“完全限定”的名称引用而非值引用。这意味着只有函数名称被腌制,以及定义函数的模块的名称。函数的代码或其任何函数属性都不会被pickle。因此,定义模块必须可以在unpickling环境中导入,并且模块必须包含命名对象,否则将引发异常。

换句话说,当您在第二个应用中取消选中Prime个对象时,它将成为Prime类的第二个应用版本的实例,即使它来自第一个应用程序。


更一般地说,酸洗旨在将要在同一应用程序中读回的数据序列化,或者至少在共享所有相关代码的非常紧密耦合的应用程序中。如果您想要更多的解耦交换机制,请考虑使用JSON或YAML。


但是,让我们说你知道所有这些,而且你确实想要出于某种原因挑选方法实现。你能这样做吗?

当然可以。它只会做很多工作,而且有点hacky。既然你试图做一些Python明确不想做的事情,你就不得不期待那样。

首先,您需要编写一个代码选择器,它传递足够的信息,您可以使用它调用types.CodeType构造函数。这是实现细节与语言深层次之间的界限,所以你可以看到构造函数参数的唯一地方就是在交互式控制台上输入help,这是唯一的方法。通过查看the table in the inspect docs并猜测哪个参数与成员一起使用,来判断这些参数是什么意思。 (这非常简单 - argcountco_argcount等一起使用。)

您会注意到其中一些code成员实际上可能没有意义。例如,如果接收者没有在文件系统中的相同路径上拥有这些文件,您真的想要传递co_filenameco_firstlineno吗? (这会导致生成回溯的错误,而不仅仅是没有源信息的回溯。)

无论如何,选择器只是创建和挑选你想要的任何一个成员的元组,并且反叛者反过来。但是你可能想要在其中填充sys.version_info或其他标记,这样你就不会尝试取消你不能运行的字节码。 (查看.pyc文件如何工作以获取详细信息。)

接下来,您必须对函数类型执行相同的操作 - 这当然会为其代码对象调用code pickler。

所以,现在你有了可以挑选和取消功能(包括代码)的代码。你有什么用呢?

当您编写Primes.__getstate__和/或Primes.__reduce__时,您可以覆盖正常的pickle机制,将func方法的实现视为对象状态的一部分

如果真的想要,你可以挑选元类,因此func方法可以作为课程状态的一部分进行腌制,这意味着你可以ll最终在类dict上使用普通的实例方法描述符,而不是塞进到对象的dict中的绑定方法。但是,如果你能同时拥有" local"和/或未打开的Primes个对象。

答案 1 :(得分:1)

对第一个问题:不。这不是酸洗的工作方式。 但是对于你的第二个问题,有一个简单的方法可以做到这一点。 您使用一个名为marshal的模块,它允许您序列化函数的代码,并且您可以将其与您的对象副本存储在某处。

import marshal
import pickle
class A:
    def __init__(self):
        self.a = 1
    def func(self):
        print self.a
firstA = A()
s = pickle.dumps(firstA)
sf = marshal.dumps(A.func.func_code)

在其他地方,您有一个不同的A类:

class A:
    def __init__(self):
        self.a = 2
    def func(self):
        print "Hello world"

当你只加载对象时,它仍将使用不正确的函数:

secondA = pickle.loads(s)
secondA.func() #prints "Hello world"

然后,您可以使用函数代码的值来调用正确的函数:

#change the function to match what was stored
secondA.func.__func__.func_code = marshal.loads(sf)
secondA.func() #prints 1