2.9.4 深副本中的递归
为了避免复制递归数据结构可能带来的问题,deepcopy()使用了一个字典来跟踪已复制的对象。将这个字典传入__deepcopy__()方法,这样在该方法中也可以检查这个字典。
下面的例子显示了通过实现__deepcopy__()方法可以帮助一个互连的数据结构(如有向图)避免递归。
import copy
class Graph:
def __init__(self,name,connections):
self.name = name
self.connections = connections
def add_connection(self,other):
self.connections.append(other)
def __repr__(self):
return 'Graph(name={},id={})'.format(self.name,id(self))
def __deepcopy__(self,memo):
print('\nCalling __deepcopy__ for {!r}'.format(self))
if self in memo:
existing = memo.get(self)
print(' Already copied to {!r}'.format(existing))
return existing
print(' Memo dictionary:')
if memo:
for k,v in memo.items():
print(' {}:{}'.format(k,v))
else:
print(' (empty)')
dup = Graph(copy.deepcopy(self.name,memo),[])
print(' Copying to new object {}'.format(dup))
memo[self] = dup
for c in self.connections:
dup.add_connection(copy.deepcopy(c,memo))
return dup
root = Graph('root',[])
a = Graph('a',[root])
b = Graph('b',[a,root])
root.add_connection(a)
root.add_connection(b)
dup = copy.deepcopy(root)
Graph类包含一些基本的有向图方法。可以利用一个名和一个列表(包含已的现有节点)初始化一个Graph实例。add_connection()方法用于建立双向连接。深复制操作符也用到了这个方法。deepcopy()方法将打印消息来显示这个方法如何调用的,并根据需要管理备忘字典内容。它不是复制整个连接列表,而是创建一个新列表,再把各个连接的副本追加到这个列表。这样可以确保复制各个新节点时会更新备忘字典,而避免递归问题或多余的节点副本。与前面一样,完成时会返回复制的对象。
图2-1 带环对象图的深副本
图2-1中的图有几个环,不过利用备忘字典处理递归就可以避免遍历导致栈溢出错误。复制根节点root时,会生成以下输出:
第二次遇到root节点时,正在复制a节点,deepcopy()检测到递归,会重用备忘字典中现有的值,而不是创建一个新对象。