【问题标题】:Creating a deepcopy of class instance with nested weakref to it使用嵌套的弱引用创建类实例的深层副本
【发布时间】:2018-02-22 12:10:16
【问题描述】:

我有两个类:一个父类和一个容器类。父类实例有匹配的容器类实例作为弱引用。

深拷贝父实例时出现问题,weakref 仍然链接到原始实例。这是一个最小的例子:

import weakref
from copy import deepcopy


class Container:    
    def __init__(self, parent):
        self.parent = weakref.ref(parent)


class Parent:
    def __init__(self):
        self.container = Container(self)


if __name__ == '__main__':
    parent1 = Parent()
    assert(parent1 is parent1.container.parent())

    parent2 = deepcopy(parent1)
    assert(parent2 is parent2.container.parent())

第二个断言失败。

我怀疑__deepcopy__这个魔术方法可以实现,但不确定如何实现。

【问题讨论】:

    标签: python python-3.5 deep-copy weak-references


    【解决方案1】:

    问题是deepcopy 不会跟随weakref.ref 链接。它甚至没有复制weakref.ref:

    >>> from copy import deepcopy
    >>> import weakref
    >>>
    >>> parent1 = Parent()
    >>> ref1 = weakref.ref(parent1)
    >>> ref2 = deepcopy(ref1)
    >>> ref1 is ref2
    True
    

    这是明确硬编码的in the copy module。我不知道这是为什么,但我怀疑他们有他们的原因。

    但是您可以实现__deepcopy__ 方法:

    import weakref
    from copy import deepcopy
    
    class Container:    
        def __init__(self, parent):
            self.parent = weakref.ref(parent)
    
    class Parent:
        def __init__(self):
            self.container = Container(self)
    
        def __deepcopy__(self, memo):
            # set __deepcopy__ element to "false"-ey value so we don't go into
            # recusion.
            self.__deepcopy__ = None
            try:
                new = deepcopy(self, memo)
            finally:
                # Always delete the self.__deepcopy__ again, even if deepcopying failed
                del self.__deepcopy__
            del new.__deepcopy__  # remove the copied __deepcopy__ attribute
            new.container.parent = weakref.ref(new)
            return new
    
    if __name__ == '__main__':
        parent1 = Parent()
        assert parent1 is parent1.container.parent()
    
        parent2 = deepcopy(parent1)
        assert parent2 is parent2.container.parent()
    
        parent3 = deepcopy(parent2)
        assert parent3 is parent3.container.parent()
    

    由于临时的__deepcopy__ 实例属性,它有点难看。但它允许在 self 上使用普通的 deepcopy 函数,而无需进入无限递归,然后您只需手动创建对父级的新弱引用。

    您甚至可以在不临时设置__deepcopy__ 的情况下执行此操作(它应该可以正常工作):

    import weakref
    from copy import deepcopy
    
    class Container:    
        def __init__(self, parent):
            self.parent = weakref.ref(parent)
    
    class Parent:
        def __init__(self):
            self.container = Container(self)
    
        def __deepcopy__(self, memo):
            # Create a new class
            new = object.__new__(type(self))
            memo[id(self)] = new   # add the new class to the memo
            # Insert a deepcopy of all instance attributes
            new.__dict__.update(deepcopy(self.__dict__, memo))
            # Manually update the weakref to be correct
            new.container.parent = weakref.ref(new)
            return new
    
    if __name__ == '__main__':
        parent1 = Parent()
        assert parent1 is parent1.container.parent()
    
        parent2 = deepcopy(parent1)
        assert parent2 is parent2.container.parent()
    
        parent3 = deepcopy(parent2)
        assert parent3 is parent3.container.parent()
    

    【讨论】:

    • 现在尝试对 parent2 进行深度复制,看看新副本的容器的父级是什么。 (temporarly_set_attribute 并不能完全完成您需要的所有清理工作。此外,它的拼写错误。)就个人而言,我会避免在 self__deepcopy__ 中调用 deepcopy
    • @user2357112 哈哈,好收获!我应该发现__deepcopy__ 实例属性也被复制了。现已修复(并且没有 contextmanager...)。 :)
    • @user2357112 你的意思是像自己复制实例属性(self.__dict__)然后只更新new.__dict__
    • 是的,类似的。
    猜你喜欢
    • 2023-03-05
    • 1970-01-01
    • 2016-01-12
    • 2023-02-22
    • 2012-05-16
    • 1970-01-01
    • 2016-03-02
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多