pickle的`persistent_id`有哪些替代方案?

5
我一直在使用Python的pickle模块实现一个薄的基于文件的持久性层。这个持久性层(作为一个更大的库的一部分)严重依赖pickle的persistent_id功能,将指定类的对象保存为单独的文件。
这种方法唯一的问题是pickle文件不可人工编辑,我更希望以文本格式保存并可用文本编辑器进行编辑(例如YAML或JSON)。
您是否知道任何使用可人工编辑格式的库,并提供类似于pickle的persistent_id功能?或者,您有没有关于在基于YAML或JSON的序列化库之上实现它们的建议,而无需重写pickle的大量子集?

没有对持久性系统的目的或要求进行描述,很难就如何实现一个持久性系统提出建议。 - taleinat
@taleinat,这个问题的范围比实现持久化系统要窄:是否有任何库提供类似于_pickle_的persistent_id机制的功能,但使用可由人编辑的格式?(但你是对的,最后一部分可能会引起一些混淆 - 我会尝试重新措辞) - Riccardo Murri
1个回答

4
我自己还没有尝试过,但我认为你应该可以使用PyYAML优雅地完成这个任务,使用他们所谓的"representers"和"resolvers"
编辑
在与发布者进行了广泛的评论交流后,以下是使用PyYAML实现所需行为的方法。
重要说明:如果一个Persistable实例有另一个这样的实例作为属性,或者以某种方式包含在其属性之一中,则包含的Persistable实例将不会保存到另一个单独的文件中,而是将内联保存在与父Persistable实例相同的文件中。据我所知,这种限制也存在于发布者基于pickle的系统中,并且可能适用于他/她的用例。我还没有找到一种优雅的解决方案,不涉及yaml.representer.BaseRepresenter的黑客攻击。
import yaml
from functools import partial

class Persistable(object):
    # simulate a unique id
    _unique = 0

    def __init__(self, *args, **kw):
        Persistable._unique += 1
        self.persistent_id = ("%s.%d" %
                              (self.__class__.__name__, Persistable._unique))

def persistable_representer(dumper, data):
    id = data.persistent_id
    print "Writing to file: %s" % id
    outfile = open(id, 'w')
    outfile.write(yaml.dump(data))
    outfile.close()
    return dumper.represent_scalar(u'!xref', u'%s' % id)

class PersistingDumper(yaml.Dumper):
    pass

PersistingDumper.add_representer(Persistable, persistable_representer)
my_yaml_dump = partial(yaml.dump, Dumper=PersistingDumper)

def persistable_constructor(loader, node):
    xref = loader.construct_scalar(node)
    print "Reading from file: %s" % id
    infile = open(xref, 'r')
    value = yaml.load(infile.read())
    infile.close()
    return value

yaml.add_constructor(u'!xref', persistable_constructor)


# example use, also serves as a test
class Foo(Persistable):
    def __init__(self):
        self.one = 1
        Persistable.__init__(self)

class Bar(Persistable):
    def __init__(self, foo):
        self.foo = foo
        Persistable.__init__(self)

foo = Foo()
bar = Bar(foo)
print "=== foo ==="
dumped_foo = my_yaml_dump(foo)
print dumped_foo
print yaml.load(dumped_foo)
print yaml.load(dumped_foo).one

print "=== bar ==="
dumped_bar = my_yaml_dump(bar)
print dumped_bar
print yaml.load(dumped_bar)
print yaml.load(dumped_bar).foo
print yaml.load(dumped_bar).foo.one

baz = Bar(Persistable())
print "=== baz ==="
dumped_baz = my_yaml_dump(baz)
print dumped_baz
print yaml.load(dumped_baz)

从现在开始,当你想要将Persistable类的实例保存到单独的文件中时,请使用my_yaml_dump代替yaml.dump。但是不要在persistable_representerpersistable_constructor中使用它!没有特殊的加载函数是必要的,只需使用yaml.load
哎呀,这需要一些工作...我希望这可以帮到你!

非常感谢您的建议!但是,我无法轻松地使其工作(至少不容易):https://gist.github.com/1348843 主要问题:(1)PyYAML仅基于对象的主类来决定使用哪个表示器,因此添加属性“persistent_id”或将对象子类化为指定的“Persistent”类都不起作用;(2)因此,表示器函数无法决定是使用“交叉引用”表示还是普通的YAML表示:这需要告诉表示器何时作为转储另一个对象的一部分调用,或者“本地”调用-否则我们会得到无限递归。 - Riccardo Murri
你的代码进入了无限递归,因为你在 representer 中调用了 yaml.dump(data),并且你告诉 yaml.dump 使用该 representer 来转储数据!尝试在我提供链接的 PyYAML 文档页面中构建 representer/constructor 示例。具体来说,你应该在 representer 中使用 dumper.represent_scalar 而不是 yaml.dump,在 constructor 中使用 loader.construct_scalar 而不是 yaml.load - taleinat
是的,我知道为什么它会进入无限递归。问题在于我不能使用represent_scalar/construct_scalar函数,因为“持久”对象不是简单的标量,它们是Python对象,我必须获得一个“常规”的YAML表示。这就是pickle的persistent_id所做的:它生成正常的pickled表示,但将其保存到另一个文件中。 - Riccardo Murri
我明白了,你想要再次使用yaml.dump将对象转换为YAML格式,但是不需要特殊处理,并将其保存到文件中,对吗?如果是这样的话,使用pickle进行相同操作会遇到相同的问题吗?你是如何在基于pickle的解决方案中克服这个问题的? - taleinat
是的,正确的。在基于pickle的解决方案中,我没有做任何特殊的魔法;这是pickle的一个功能,在此文档中有记录(http://docs.python.org/library/pickle.html#pickling-and-unpickling-external-objects)。 - Riccardo Murri
在网站的示例中,创建了一个特定的pickle.Pickler实例,并且仅在该实例上设置了persistent_id钩子。如果在persistent_id内部使用了pickle.dumppickle.dumps,它们将使用不带有persistent_id的不同Pickler,因此它会起作用。我将使用PyYAML更新我的答案以执行此操作的方法。 - taleinat

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接