2

据我了解pickle,您可以在文件和项目之间发送对象,只要这些对象的类存在于两个命名空间中。我有两个将传递 Prime 对象的应用程序。

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

这两个应用程序都以上述版本的Prime. 但是第一个应用程序会改变它的功能Prime,这样func就会print("hello world")。然后,第二个应用程序将接收第一个版本的Prime通过 pickle 并使用它,以便:

第二个.py:

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

输出:

1
hello world

我的两部分问题是这个。Prime如果第二个应用程序的命名空间中只有原始版本的 如果是这样,我该如何更改 first's 的功能Prime.func

4

3 回答 3

2

唯一正确的方法是让两个应用程序使用定义Prime类的任何模块的完全相同的版本,可在完全相同的限定名称下导入。至于什么可以腌制和解封?解释:

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

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


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


但是,假设您知道所有这些,并且出于某种原因确实想要腌制方法实现。你能做到吗?

你当然可以。这将是很多工作,而且有点hacky。由于您正在尝试做一些 Python 明确尝试不做的事情,所以您不得不期待这一点。

首先,您需要编写一个代码选择器来传递足够的信息,以便您可以types.CodeType使用它调用构造函数。那是实现细节与语言的深层部分之间的界限,所以你可以看到构造函数参数的唯一地方是help在交互式控制台上输入,而你可以知道这些参数意味着什么的唯一方法是查看文档中的表格inspect并猜测哪个参数与成员一起使用。(这很简单——<code>argcount 与co_argcount等一起使用。)

您会注意到其中一些code成员实际上可能没有意义。例如,您是否真的想通过co_filename,并且co_firstlineno如果接收方不打算在文件系统中的同一路径上拥有这些文件?(这将导致错误生成回溯,而不仅仅是没有源信息的回溯。)

无论如何,pickler 只是创建并腌制你想要的任何成员的元组,而 unpickler 则相反。但是您可能想在其中填充sys.version_info或其他一些标记,这样您就不会尝试解开您无法运行的字节码。(查看 .pyc 文件的工作原理以了解详细信息。)

接下来你必须对函数类型做同样的事情——这当然会code为它们的代码对象调用pickler。

所以,现在你有了可以pickle和unpickle函数的代码(包括它的代码)。这对你有什么好处?

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

如果你真的想要,你可以腌制元类,所以func方法可以作为类状态的一部分腌制,这意味着你最终会在类字典上得到一个普通的实例方法描述符,而不是塞进一个绑定的方法对象的字典。但是,如果您可以同时拥有“本地”对象和未腌制Primes对象,那将无法正常工作。

于 2013-07-02T19:00:18.297 回答
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
于 2013-07-02T19:27:21.560 回答
0

这已经晚了 8 年,但是这个问题仍然出现在谷歌搜索中,所以有人可能会从这个答案中受益。

默认情况下,腌制一个对象将存储该对象的类__module____name__该对象自己的__dict__。Unpickling 查找并导入同名的本地模块(可能是也可能不是您在 pickling 时使用的模块!),然后在该模块中找到一个具有 right 的类__name__,创建该类的一个新实例,并给它存储的__dict__属性。

这里的关键点是 unpickler 不一定会打开与定义原始对象的类完全相同的模块,只要它可以找到具有相同名称的任何本地模块即可。假设您调用了mod包含您的类的模块Prime。因此,要让 OP 的第二个应用程序为其 Primes 提供不同的功能,您需要做的就是将第二个应用程序定位在与第一个应用程序不同的位置,以便当第二个应用程序尝试查找本地应用程序时名为modwhat it will find 的模块是一个名称相似的模块,其func将具有第二种“Hello World”功能,而不是原始模块的功能。这将使第一个应用程序中的原始 Prime 具有一种功能,而第二个应用程序中这些 Prime 的未腌制副本具有另一种功能。

(如果您想要在同一个应用程序中同时使用两种类型,就像 OP 有时似乎想要的那样,那么您需要诉诸其他诡计,我怀疑是否值得使用 pickle。例如,您可以创建具有新“func”的子类Prime,然后更改对象__class__以切换它将具有哪种功能。或者您可以使用copy()克隆对象,并变形克隆的对象__class__。)

无论如何,使用同名模块进行 unpickling可以让您在应用程序之间切换功能,但当然,您会因为两个具有同名模块的并行文件结构而引起混淆,所以我不确定我是否真的会鼓励它!可能发生这种情况的最常见方法是保存泡菜,然后编辑模块,然后将保存的文件解压到现在有效的旧名称新模块中!所以最好记住这个问题作为一个对象课程,为什么你通常需要小心使用与创建它们相同的模块重新加载泡菜,而不是其他类似命名的模块,甚至没有显着改变的版本原始模块。

于 2022-01-24T15:28:06.180 回答