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.
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'
Cyclic dependencies are a problem for
deepcopy
when you: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 innode1
's attributes, it needs to initializenode2
, which in turn relies on the partially creatednode1
(in both cases due to theedge_dict
). At the time it's trying to fill in theedge_dict
for oneNode
, theNode
it's adding toedge_dict
doesn't have itsid
attribute set yet, so the attempt to hash it fails.You can correct this by using
__new__
to ensure invariants are established prior to initializing mutable, possibly recursive attributes, and defining thepickle
helper__getnewargs__
(or__getnewargs_ex__
) to make it use them properly. Specifically, change you class definition to:Note: If this is Python 2 code, make sure to explicitly inherit from
object
and changesuper()
tosuper(Node, cls)
in__new__
; the code given is the simpler Python 3 code.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 importscopy
), and otherwise leave it untouched: