Python的__reduce __ / copy_reg语义和有状态的unpickler

时间:2012-08-22 02:35:16

标签: python pickle

我想为属于我的扩展库的对象实现pickle支持。在启动时初始化了一个类Service的全局实例。所有这些对象都是由于某些Service方法调用而生成的,并且基本上属于它。服务知道如何将它们序列化为二进制缓冲区以及如何将缓冲区反序列化为对象。

似乎Pythons __ reduce__应该符合我的目的 - 实施酸洗支持。我开始实现一个并意识到unpickler存在问题(第一个元素是一个元组,预期由__ reduce__返回)。这个unpickle函数需要Service的实例才能将输入缓冲区转换为Object。这里有一些伪代码来说明问题:

class Service(object):
   ...
   def pickleObject(self,obj):
      # do serialization here and return buffer
      ...

   def unpickleObject(self,buffer):
      # do deserialization here and return new Object
      ...

class Object(object):
    ...
    def __reduce__(self):
        return self.service().unpickleObject, (self.service().pickleObject(self),)

注意元组中的第一个元素。 Python pickler不喜欢它:它说它是instancemethod并且它不能被pickle。显然,pickler试图将例程存储到输出中,并希望Service实例与函数名一起使用,但这不是我想要发生的。我不希望(并且实际上不能:服务不可选)将服务与所有对象一起存储。我希望在调用pickle.load之前创建服务实例,并且在unpickling期间以某种方式使用该实例。

这是我来自copy_reg模块的地方。它似乎应该解决我的问题。该模块允许动态地为每种类型注册pickler和unpickler例程,并且这些例程应该稍后用于此类型的对象。所以我将此注册添加到服务构建中:

class Service(object):
  ...
  def __init__(self):
      ...
      import copy_reg
      copy_reg( mymodule.Object, self.pickleObject, self.unpickleObject )

self.unpickleObject现在是一个绑定方法,将服务作为第一个参数,缓冲区作为第二个参数。 self.pickleObject也是绑定方法,将服务和对象作为pickle。 copy_reg要求pickleObject例程应遵循reducer语义并返回与之前类似的元组。在这里,问题又出现了:我应该作为第一个元组元素返回什么?

class Service(object):
  ...
  def pickleObject(self,obj):
      ...
      return self.unpickleObject, (self.serialize(obj),)

在这种形式下,泡菜再次抱怨它不能腌制实例方法。我试过没有 - 它也不喜欢它。我放了一些虚拟功能。这工作 - 意味着序列化阶段很好,但在unpickling期间它调用这个虚函数而不是我在服务构造函数中为mymodule.Object类型注册的unpickler。

所以现在我很茫然。很抱歉很长的解释:我不知道如何在几行中提出这个问题。我可以总结一下这样的问题:

  1. 为什么copy_reg语义要求我从pickleObject返回unpickler例程,如果我希望独立注册一个呢?
  2. 有没有理由更喜欢copy_reg.constructor接口注册unpickler例程?
  3. 如何使pickle使用我注册的unpickler而不是流中的一个?
  4. 作为pickleObject结果值,我应该作为元组中的第一个元素返回什么?是否有“正确”的价值?
  5. 我是否正确处理了这一切?是否有不同/更简单的解决方案?

1 个答案:

答案 0 :(得分:3)

首先,copy_reg模块在​​这里不太可能对你有所帮助:它主要是一种向没有该方法的类添加__reduce__类似功能而不是提供任何特殊功能的方法能力(例如,如果你想从一些本身不支持它的库中挑选对象)。

__reduce__返回的可调用对象需要在要对其进行取消对象的环境中进行定位,因此实例方法并不合适。如Pickle documentation中所述:

  

在unpickling环境中,此对象必须是一个类,一个可调用的注册为   “安全构造函数”(见下文),或者它必须具有属性   __safe_for_unpickling__具有真实价值。

因此,如果您按如下方式定义了一个函数(而不是方法):

def _unpickle_service_object(buffer):
    # Grab the global service object, however that is accomplished
    service = get_global_service_object()
    return service.unpickleObject(buffer)

_unpickle_service_object.__safe_for_unpickling__ = True

现在,您可以在_unpickle_service_object方法的返回值中使用此__reduce__函数,以便在取消标记时将对象链接到新环境的全局Service对象。