【问题标题】:Changing the function of a Pickled Object更改腌制对象的功能
【发布时间】:2013-07-02 18:38:30
【问题描述】:

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

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

两个应用程序都以上述版本的Prime 开头。但是第一个应用程序将更改Prime 的功能,例如funcprint("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

我的问题分为两部分。如果第二个应用程序在其命名空间中只有原始版本的Prime,它是否能够与第一个应用程序一起工作,只要类和方法名没有更改?如果是这样,我该如何更改 first 的 Prime.func 的功能?

【问题讨论】:

  • 首先,您的意思是i = Prime()
  • 第二,你真的不能使用pickle 在不同的应用程序之间发送对象,这些应用程序具有不同的模块和具有相同名称的类,没有很多hacky 解决方法。即使您知道这通常是个坏主意,因为您确定在这种情况下不是这样,您是否特别询问如何针对您的特定情况执行这些 hacky 变通办法?
  • 最后,通常情况下,pickle 永远不会真正腌制 function 对象,只是 fully-qualified references to their names。解决这个问题的唯一方法是实现你自己的__reduce__ 和朋友,他们会做一些事情,比如腌制co_code 和其他从头开始重建代码和功能所需的信息(并检查它是否适用于正确的 Python版本,就像 .pyc 文件一样)。如果这对你来说听起来像是希腊语,你甚至不应该考虑这个。

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


【解决方案1】:

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

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

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


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


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

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

首先,您需要编写一个代码选择器来传递足够的信息,您可以使用它调用types.CodeType 构造函数。那是实现细节与语言的深层部分之间的界限,因此您可以看到构造函数参数的唯一地方是在交互式控制台上键入help,并且您可以知道这些参数是什么的唯一方法 mean 是通过查看the table in the inspect docs 并猜测while 成员使用哪个参数。 (这很简单——argcountco_argcount 一起使用,等等)

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

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

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

所以,现在您有了可以腌制和取消腌制函数(包括其代码)的代码。这对你有什么好处?

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

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

【讨论】:

    【解决方案2】:

    对于第一个问题:不。酸洗不是这样工作的。 但是对于您的第二个问题,有一种简单的方法可以做到这一点。 您使用一个名为 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
    

    【讨论】:

      【解决方案3】:

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

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

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

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

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

      【讨论】:

        猜你喜欢
        • 2016-03-27
        • 2018-10-30
        • 2022-07-16
        • 1970-01-01
        • 2021-08-18
        • 2020-06-26
        • 1970-01-01
        • 1970-01-01
        • 2019-12-14
        相关资源
        最近更新 更多