代码之家  ›  专栏  ›  技术社区  ›  Evgeny Tanhilevich

使用python deepcopy时的AttributeError

  •  5
  • Evgeny Tanhilevich  · 技术社区  · 7 年前

    __eq__ __hash__ 重写,使其对象充当字典键。每个对象还携带一个字典,由同一类的其他对象键入。我有一个奇怪的 AttributeError 当我试着 deepcopy 整个结构。我在OsX上使用Python 3.6.0。

    Python docs 深度复制 使用 memo 字典来缓存它已经复制的对象,因此嵌套结构不应该是一个问题。那么我做错了什么?我应该自己编码吗 __deepcopy__

    from copy import deepcopy
    
    
    class Node:
    
        def __init__(self, p_id):
            self.id = p_id
            self.edge_dict = {}
            self.degree = 0
    
        def __eq__(self, other):
            return self.id == other.id
    
        def __hash__(self):
            return hash(self.id)
    
        def add_edge(self, p_node, p_data):
            if p_node not in self.edge_dict:
                self.edge_dict[p_node] = p_data
                self.degree += 1
                return True
            else:
                return False
    
    if __name__ == '__main__':
        node1 = Node(1)
        node2 = Node(2)
        node1.add_edge(node2, "1->2")
        node2.add_edge(node1, "2->1")
        node1_copy = deepcopy(node1)
    

    File ".../node_test.py", line 15, in __hash__
        return hash(self.id)
    AttributeError: 'Node' object has no attribute 'id'
    
    1 回复  |  直到 6 年前
        1
  •  10
  •   ShadowRanger    7 年前

    循环依赖性对于 deepcopy 当你:

    1. 建设 ,而不仅仅是 初始化

    问题是取消勾选对象( 深度复制 默认情况下,通过pickle和unpickle复制自定义对象,除非特殊 __deepcopy__ 方法)在不初始化的情况下创建空对象,然后尝试逐个填充其属性。当它试图填补 node1 的属性,它需要初始化 node2 节点1 (在这两种情况下,由于 edge_dict 边缘dict 一个人 Node 节点 它增加了 没有它的 id 属性尚未设置,因此对其进行哈希的尝试失败。

    __new__ 确保在初始化可变(可能是递归)属性和定义 pickle 帮手 __getnewargs__ (或 __getnewargs_ex__ )使其正确使用。具体来说,将类定义更改为:

    class Node:
        # __new__ instead of __init__ to establish necessary id invariant
        # You could use both __new__ and __init__, but that's usually more complicated
        # than you really need
        def __new__(cls, p_id):
            self = super().__new__(cls)  # Must explicitly create the new object
            # Aside from explicit construction and return, rest of __new__
            # is same as __init__
            self.id = p_id
            self.edge_dict = {}
            self.degree = 0
            return self  # __new__ returns the new object
    
        def __getnewargs__(self):
            # Return the arguments that *must* be passed to __new__
            return (self.id,)
    
        # ... rest of class is unchanged ...
    

    object 和改变 super() super(Node, cls) 在里面 __新建__ ; 给出的代码是更简单的Python 3代码。

    只有 copy.deepcopy __新建__ / (需要新样式的类)将仅覆盖深度复制。您需要在原始类上定义以下额外方法(并确保模块导入 copy ),否则将其保持不变:

    def __deepcopy__(self, memo):
        # Deepcopy only the id attribute, then construct the new instance and map
        # the id() of the existing copy to the new instance in the memo dictionary
        memo[id(self)] = newself = self.__class__(copy.deepcopy(self.id, memo))
        # Now that memo is populated with a hashable instance, copy the other attributes:
        newself.degree = copy.deepcopy(self.degree, memo)
        # Safe to deepcopy edge_dict now, because backreferences to self will
        # be remapped to newself automatically
        newself.edge_dict = copy.deepcopy(self.edge_dict, memo)
        return newself