【问题标题】:How to preserve references when pickling in Python在 Python 中酸洗时如何保留引用
【发布时间】:2021-10-16 10:11:14
【问题描述】:

我想腌制一个对象以便能够存储/恢复我的会话。 该对象包含对其他对象的多个引用。

我知道引用的对象会被它腌制,但是在取消腌制时引用会发生变化。我也知道将对象腌制在一起可以维护引用,尽管这对我来说很复杂,因为我需要腌制整个复杂的对象结构。

示例

p1 = Point()
p2 = Point()
p1.nearest_point = p2
p2.nearest_point = p1

line = Line(p1, Point())

with open("pickled", "wb") as file:
    pickler = pickle.Pickler(file)
    pickler.dump(p1)
    pickler.dump(p2)

with open("pickled", "rb") as file:
    pickler = pickle.Unpickler(file)
    p1 = pickler.load()
    p2 = pickler.load()

# True: Reference between the two pickled objects is maintained
assert p1.nearest_point == p2

# False: Reference between not-pickled and pickled objects is broken, leading to a duplicate object (original, unpickled)
assert line.pointA == p1

在这种情况下,解决方案也可能是对 Line 对象进行腌制,但在我的实际情况中,我正在处理一个更复杂的结构,其中对结构的每个部分进行腌制/取消腌制肯定会导致疏忽和错误。

我该如何正确处理这个问题?

【问题讨论】:

标签: python pickle


【解决方案1】:

Pickle 通过跟踪对象的地址来维护引用。在 unpickling 时,对象被重建。根据用例,三种方法可以实现您的目标:

  1. 解封时重新引用内存对象。如果您的腌制数据在程序之外发生更改,它将被丢弃。
  2. 在 unpickling 和尝试合并更改时重新引用内存对象。
  3. 使用 unpickled obj 更新对内存对象的任何引用。

在以下解决方案中,我假设我们采用方法 2:

实用程序函数基于“Tiran”在博客 discussion 中发表的(现已删除)评论 @Hophat Abc 在 his own answer 中的引用,该评论适用于 Python 2 和 3。

免责声明:实用功能不安全。您可能需要在其上添加自己的安全防护,或使用其他方式取消对 id 的引用。此外,使用 id 也不安全。代码只是为了演示,但你明白了。

import _ctypes

def di(obj_id):
    """ Inverse of id() function. """
    return _ctypes.PyObj_FromPtr(obj_id)

def merge(obj1, obj2):
    """Merge changes."""
    obj1.__dict__ = obj2.__dict__
    return obj1

p1 = Point()
p2 = Point()
p1.nearest_point = p2
p2.nearest_point = p1

line = Line(p1, Point())

with open("pickled", "wb") as file:
    pickler = pickle.Pickler(file)

    seen = {}
    def persistent_id(obj):
        # Bypass ids
        if type(obj) == int:
            return None

        if id(obj) in seen:
            return id(obj)
        else:
            seen[id(obj)] = True
            # pickle id with the object
            return (id(obj), obj)

    pickler.persistent_id = persistent_id
    pickler.dump(p1)
    pickler.dump(p2)

with open("pickled", "rb") as file:
    unpickler = pickle.Unpickler(file)

    def persistent_load(pid):
        if type(pid) == tuple:
            # Call di for dereferencing
            return merge(di(pid[0]), pid[1])
        else:
            return di(pid)
    
    unpickler.persistent_load = persistent_load
    p1 = pickler.load()
    p2 = pickler.load()

# True: Reference between the two pickled objects is maintained
assert p1.nearest_point == p2

# True: Referenceing restored.
assert line.pointA == p1

【讨论】:

    猜你喜欢
    • 2012-11-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-05-29
    • 2011-11-22
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多