使用python deepcopy时发生AttributeError [英] AttributeError when using python deepcopy

查看:168
本文介绍了使用python deepcopy时发生AttributeError的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个覆盖了 __ eq __ __ hash __ 的类,以使其对象用作字典键。每个对象还带有一个字典,该字典由相同类的其他对象作为键。当我尝试对整个结构进行 deepcopy 时,出现一个奇怪的 AttributeError 。我在OsX上使用Python 3.6.0。

I have a class that has __eq__ and __hash__ overridden, to make its objects act as dictionary keys. Each object also carries a dictionary, keyed by other objects of the same class. I get a weird AttributeError when I try to deepcopy the whole structure. I am using Python 3.6.0 on OsX.

来自 Python文档,它看起来好像 deepcopy 使用备忘录字典来缓存已复制的对象,因此嵌套结构应该不是问题。那我在做什么错呢?我是否应该编写自己的 __ deepcopy __ 方法来解决此问题?

From Python docs it looks as if deepcopy uses a memo dictionary to cache the objects it has already copied, so nested structures should not be a problem. What am I doing wrong then? Should I code up my own __deepcopy__ method to work around this? How?

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'


推荐答案

循环依赖项是 deepcopy 的问题,当您:

Cyclic dependencies are a problem for deepcopy when you:


  1. 必须对类进行哈希处理并包含引用循环,

  2. 不确保在对象 construction 上建立哈希相关(和与等式相关)不变式,而不仅仅是 initialization

  1. Have classes that must be hashed and contain reference cycles, and
  2. Don't ensure hash-related (and equality related) invariants are established at object construction, not just initialization

问题在于取消拾取对象( deepcopy 默认情况下是复制自定义对象除非通过定义特殊的 __ deepcopy __ 方法,然后通过酸洗和酸洗来创建对象),然后创建空对象而不对其进行初始化,然后尝试逐个填充其属性。当它尝试填写 node1 的属性时,它需要初始化 node2 ,这又依赖于部分创建了 node1 (在两种情况下都由于 edge_dict )。当时,它试图为一个 Node 节点,即 Node edge_dict 填充它添加到 edge_dict 时尚未设置其 id 属性,因此尝试进行散列

The problem is unpickling an object (deepcopy, by default, copies custom objects by pickling and unpickling, unless a special __deepcopy__ method is defined) creates the empty object without initializing it, then tries to fill in its attributes one by one. When it tries to fill in node1's attributes, it needs to initialize node2, which in turn relies on the partially created node1 (in both cases due to the edge_dict). At the time it's trying to fill in the edge_dict for one Node, the Node it's adding to edge_dict doesn't have its id attribute set yet, so the attempt to hash it fails.

您可以通过使用 __ new __ 来纠正此问题,以确保在初始化可变的(可能是递归的)之前已建立不变量属性,并定义 pickle 助手 __ getnewargs __ (或 __ getnewargs_ex __ ),以使其正确使用它们。具体来说,将您的类定义更改为:

You can correct this by using __new__ to ensure invariants are established prior to initializing mutable, possibly recursive attributes, and defining the pickle helper __getnewargs__ (or __getnewargs_ex__) to make it use them properly. Specifically, change you class definition to:

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 ...

注意:如果这是Python 2代码,请确保从 object 显式继承并更改 super() __ new __ 中的 super(Node,cls);给出的代码是更简单的Python 3代码。

Note: If this is Python 2 code, make sure to explicitly inherit from object and change super() to super(Node, cls) in __new__; the code given is the simpler Python 3 code.

另一种解决方案,仅处理 copy.deepcopy ,而不支持酸洗或要求使用 __ new __ / __ getnewargs __ (需要使用新式类)将仅覆盖深度复制。您将在原始类上定义以下额外方法(并确保模块导入 copy ),否则保持不变:

An alternate solution that handles only copy.deepcopy, without supporting pickling or requiring the use of __new__/__getnewargs__ (which require new-style classes) would be to override deepcopying only. You'd define the following extra method on your original class (and make sure the module imports copy), and otherwise leave it untouched:

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

这篇关于使用python deepcopy时发生AttributeError的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆