【问题标题】:I can "pickle local objects" if I use a derived class?如果我使用派生类,我可以“腌制本地对象”吗?
【发布时间】:2016-05-03 02:56:22
【问题描述】:

pickle 引用 states that 可以腌制的对象集相当有限。确实,我有一个函数可以返回一个动态生成的类,但我发现我无法腌制该类的实例:

>>> import pickle
>>> def f():
...     class A: pass
...     return A
... 
>>> LocalA = f()
>>> la = LocalA()
>>> with open('testing.pickle', 'wb') as f:
...     pickle.dump(la, f, pickle.HIGHEST_PROTOCOL)
... 
Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
AttributeError: Can't pickle local object 'f.<locals>.A'

这样的对象对于pickle 来说太复杂了。行。现在,神奇的是,如果我尝试腌制一个类似的对象,但它是派生类的,它可以工作!

>>> class DerivedA(LocalA): pass
... 
>>> da = DerivedA()
>>> with open('testing.pickle', 'wb') as f:
...     pickle.dump(da, f, pickle.HIGHEST_PROTOCOL)
...
>>>

这里发生了什么?如果这很容易,为什么pickle 不使用此解决方法来实现允许“本地对象”被腌制的dump 方法?

【问题讨论】:

    标签: python python-3.x nested pickle


    【解决方案1】:

    我认为你没有仔细阅读the reference you cite。该参考资料还明确指出,只有以下对象是可腌制的:

    • 在模块顶层定义的函数(使用 def,而不是 >lambda)
    • 在模块顶层定义的内置函数
    • 模块顶层定义的类

    你的例子

    >>> def f():
    ...     class A: pass
    ...     return A
    

    不在模块的顶层定义一个类,它在f()范围 内定义一个类。 pickle 适用于全局类,而不是本地类。这会自动使可腌制测试失败。

    DerivedA 是一个全局类,所以一切正常。

    至于为什么只有顶级(对您来说是全局的)类和函数不能被腌制,参考也回答了这个问题(我的粗体):

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

    同样,类是通过命名引用来腌制的,因此在 unpickling 环境中也适用相同的限制。

    所以你有它。 pickle 仅通过名称引用序列化对象,而不是通过对象中包含的原始指令。这是因为pickle's 的工作是序列化对象层次结构,仅此而已。

    【讨论】:

    • 好了,只腌制类名。我认为类本身已保存(以某种方式),然后这意味着保存它的基类,这是不可腌制的。
    • 顺便说一句,如果我在有一个不相关的类但同名的环境中解压它,我会得到这个不相关的类的一些弗兰克斯坦怪物对象,但具有旧的属性班级?我认为这可以归结为:腌制对象不像我想象的那样独立。
    • @fonini 同样来自参考:“......当类实例被腌制时,它们的类的代码和数据不会随之腌制。只有实例数据被腌制。”所以,是的,那听起来像是正确的行为。 :)
    • 我完全跨过了。
    【解决方案2】:

    我不同意,你可以腌制两者。您只需要使用更好的序列化程序,例如dilldill(默认情况下)通过保存类定义而不是通过引用进行腌制来腌制类,因此它不会使您的第一个案例失败。如果您愿意,您甚至可以使用dill 获取源代码。

    >>> import dill as pickle
    >>> def f():
    ...   class A: pass
    ...   return A
    ... 
    >>> localA = f()
    >>> la = localA()
    >>> 
    >>> _la = pickle.dumps(la)
    >>> la_ = pickle.loads(_la)
    >>>    
    >>> class DerivedA(localA): pass
    ... 
    >>> da = DerivedA()
    >>> _da = pickle.dumps(da)
    >>> da_ = pickle.loads(_da)
    >>> 
    >>> print(pickle.source.getsource(la_.__class__))
      class A: pass
    
    >>> 
    

    【讨论】:

    • 我敢打赌,重点关注不同的事物,不一定更好?还是莳萝在速度和存储对象的大小方面也更好?但感谢您提及它!
    • dill 并没有更快,而且在存储对象的大小方面也没有更好。事实上,在这两个方面它都比pickle 更糟糕。它在序列化各种类型的对象的能力方面要好得多,这就是我的意思。
    【解决方案3】:

    您只能腌制在模块顶层定义的类的实例。

    但是,如果将本地定义的类的实例提升到顶级,则可以腌制它们。

    您必须设置本地类的 __ qualname__ 类属性。然后,您必须将该类分配给同名的顶级变量。

    def define_class(name):
        class local_class:
            pass
        local_class.__qualname__ = name
        return local_class
    
    class_A = define_class('class_A') # picklable
    class_B = define_class('class_B') # picklable
    class_X = define_class('class_Y') # unpicklable, names don't match
    

    【讨论】:

    • 您的解决方案似乎是适合我的方式。你上面的例子对我有用,但是当我尝试我的实际代码时,我不断收到“Can't pickle : it's not the same object as XX.YY”。你知道我做错了什么吗?
    • 这是我的错误......我正在创建一个 class_A 的实例,然后在尝试腌制之前重新定义这个类。
    【解决方案4】:

    DerivedA 实例是可腌制的,因为 DerivedA 可通过与其完全限定名称匹配的全局变量获得,这就是 pickle 在取消腌制时查找类的方式。

    尝试对本地类做这样的事情的问题是,没有任何东西可以识别哪个 A 类实例对应。如果你运行f 两次,你会得到两个A 类,并且没有办法从另一个程序运行中判断哪个应该是未腌制的A 实例类。如果你根本不运行 f,你会得到 no A 类,那么你对 unpickled 实例的类型到底做了什么?

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2023-04-05
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-11-25
      • 2014-04-30
      相关资源
      最近更新 更多