copy——Duplicate Objects

it2023-01-07  65

目的:提供使用浅或深复制语义复制对象的功能。

复制模块包括两个函数copy()和deepcopy(),用于复制现有对象。

Shallow Copies

由copy()创建的浅拷贝是一个新容器,其中填充了对原始对象内容的引用。在对列表对象进行浅拷贝时,将构造一个新列表,并将原始对象的元素附加到该列表中。

import copy import functools @functools.total_ordering class MyClass: def __init__(self, name): self.name = name def __eq__(self, other): return self.name == other.name def __gt__(self, other): return self.name > other.name a = MyClass('a') my_list = [a] dup = copy.copy(my_list) print(' my_list:', my_list) print(' dup:', dup) print(' dup is my_list:', (dup is my_list)) print(' dup == my_list:', (dup == my_list)) print('dup[0] is my_list[0]:', (dup[0] is my_list[0])) print('dup[0] == my_list[0]:', (dup[0] == my_list[0])) For a shallow copy, the MyClass instance is not duplicated, so the reference in the dup list is to the same object that is in my_list. my_list: [<__main__.MyClass object at 0x101f9c160>] dup: [<__main__.MyClass object at 0x101f9c160>] dup is my_list: False dup == my_list: True dup[0] is my_list[0]: True dup[0] == my_list[0]: True

Deep Copies

deepcopy()创建的深拷贝是一个新容器,其中填充了原始对象内容的副本。要制作列表的深拷贝,请构造一个新列表,复制原始列表的元素,然后将这些副本附加到新列表中。 用deepcopy()代替对copy()的调用,可以明显看出输出的差异。

import copy import functools @functools.total_ordering class MyClass: def __init__(self, name): self.name = name def __eq__(self, other): return self.name == other.name def __gt__(self, other): return self.name > other.name a = MyClass('a') my_list = [a] dup = copy.deepcopy(my_list) print(' my_list:', my_list) print(' dup:', dup) print(' dup is my_list:', (dup is my_list)) print(' dup == my_list:', (dup == my_list)) print('dup[0] is my_list[0]:', (dup[0] is my_list[0])) print('dup[0] == my_list[0]:', (dup[0] == my_list[0]))

列表的第一个元素不再是相同的对象引用,但是将两个对象进行比较时,它们的结果仍然相等。

my_list: [<__main__.MyClass object at 0x101e9c160>] dup: [<__main__.MyClass object at 0x1044e1f98>] dup is my_list: False dup == my_list: True dup[0] is my_list[0]: False dup[0] == my_list[0]: True Customizing Copy Behavior It is possible to control how copies are made using the __copy__() and __deepcopy__() special methods.

调用__copy __()时不带任何参数,并且应返回对象的浅拷贝。 __deepcopy __()与备注字典一起调用,并且应返回对象的深层副本。任何需要深度复制的成员属性都应传递到copy.deepcopy()以及备忘录字典中,以控制递归。 (备忘字典将在后面详细说明。) 以下示例说明了如何调用这些方法。

import copy import functools @functools.total_ordering class MyClass: def __init__(self, name): self.name = name def __eq__(self, other): return self.name == other.name def __gt__(self, other): return self.name > other.name def __copy__(self): print('__copy__()') return MyClass(self.name) def __deepcopy__(self, memo): print('__deepcopy__({})'.format(memo)) return MyClass(copy.deepcopy(self.name, memo)) a = MyClass('a') sc = copy.copy(a) dc = copy.deepcopy(a)

备忘字典用于跟踪已经复制的值,以避免无限递归。

__copy__() __deepcopy__({})

Recursion in Deep Copy

为了避免复制递归数据结构出现问题,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类包括一些基本的有向图方法。实例可以使用名称和与其连接的现有节点的列表进行初始化。 add_connection()方法用于建立双向连接。深层复制运算符也使用它。 __deepcopy __()方法显示消息以显示其调用方式,并根据需要管理备忘录字典的内容。它没有复制整个连接列表,而是创建了一个新列表,并将各个连接的副本附加到该列表中。这样可以确保在复制每个新节点时更新备忘录字典,并且可以避免递归问题或额外的节点副本。和以前一样,该方法完成后将返回复制的对象。

Deep Copy for an Object Graph with Cycles

图中所示的图形包括几个循环,但是使用备忘录字典处理递归可防止遍历引起堆栈溢出错误。复制根节点后,将产生以下输出。

Calling __deepcopy__ for Graph(name=root, id=4326183824) Memo dictionary: (empty) Copying to new object Graph(name=root, id=4367233208) Calling __deepcopy__ for Graph(name=a, id=4326186344) Memo dictionary: Graph(name=root, id=4326183824): Graph(name=root, id=4367233208) Copying to new object Graph(name=a, id=4367234720) Calling __deepcopy__ for Graph(name=root, id=4326183824) Already copied to Graph(name=root, id=4367233208) Calling __deepcopy__ for Graph(name=b, id=4326183880) Memo dictionary: Graph(name=root, id=4326183824): Graph(name=root, id=4367233208) Graph(name=a, id=4326186344): Graph(name=a, id=4367234720) 4326183824: Graph(name=root, id=4367233208) 4367217936: [Graph(name=root, id=4326183824), Graph(name=a, id=4326186344)] 4326186344: Graph(name=a, id=4367234720) Copying to new object Graph(name=b, id=4367235000)

第二次遇到根节点时,在复制节点时,__ deepcopy __()检测到递归,并重用备忘录字典中的现有值,而不是创建新对象。

最新回复(0)