3.10. copy — 对象复制 | 数据结构 |《python 3 标准库实例教程》| python 技术论坛-金年会app官方网
目的:用浅复制或深复制的语义来为对象提供复制函数。
copy
模块为复制已存在的对象提供两个方法, copy()
和 deepcopy()
。
浅拷贝
浅复制 是用 copy()
来创建的一个填充了对原始对象引用的新容器。当创建一个 list
对象的浅复制的时候,会构造一个新的 list
并将原始对象的元素附加到它上面。
copy_shallow.py
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]))
对于浅复制, myclass
实例并没有被复制, 所以在 dup
列表和 my_list
列表中引用的其实是同一个对象。
$ python3 copy_shallow.py
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
深拷贝
由 deepcopy()
创建的 深拷贝 是一个新的容器, 容器里面填充了原始对象内容的副本。为了创建一个 list
的深拷贝,会先创建一个新的 list
对象, 复制原序列里面的元素,然后将这些副本添加到新的序列当中。
用 deepcopy()
代替 copy()
进行调用,通过输出观察两者的差异。
copy_deep.py
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]))
序列中的第一个对象的引用不再是原序列中第一个对象的引用, 但是当两个对象进行比较的时候, 他们仍然被认为是相同的。
$ python3 copy_deep.py
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
自定义复制行为
通过 __copy__()
和 __deepcopy__()
这两个特殊方法可以控制复制行为。
- 调用
__copy__()
方法的无需带任何参数,返回对象的浅拷贝。 __deepcopy__()
被备忘字典调用并且返回对象的深拷贝。任何需要深度复制的成员属性都应该和备忘字典一起传递给copy.deepcopy()
去控制递归。(备忘字典将在后面详细介绍。)
以下示例说明如何调用这些方法。
copy_hooks.py
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)
备忘字典通过追踪已经被复制的值来避免无限递归。
$ python3 copy_hooks.py
__copy__()
__deepcopy__({})
深拷贝中的递归
为了避免重复的递归数据结构, deepcopy()
用了一个字典跟踪被拷贝的对象。这个字典会被传递给 __deepcopy__()
方法,它将在这个方法内被检查。
下面的例子通过实现了 __deepcopy__()
方法展示了如何使用像有向图一样的互联数据结构防止递归的。
copy_recursion.py
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__()
方法通过打印信息来展现它是如何被调用的, 并且根据需要管理备忘录字典的内容。 他不是批量的复制整个连接列表, 而是创建一个新的列表并且将单个连接的副本附加到它。这个可以确保备忘录字典会随着每个新节点的复制而更新,避免了递归问题或节点的额外副本, 该方法在完成时返回复制的对象。
"root"; "b" -> "root"; "b" -> "a"; "root" -> "a"; "root" -> "b"; }" />
具有循环的对象图的深拷贝
图中的图形包含着几个循环, 但使用备忘录字典可以防止遍历导致的堆栈溢出错误。当 root 节点被复制的时候,它会产生以下输出。
$ python3 copy_recursion.py
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)
第二次遇到 root 节点的时候,并且 a 节点被复制的时候, __deepcopy__()
检测到递归并且重用备忘录字典中已经存在的值,而不是创建新对象。
参考
本译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接
我们的翻译工作遵照 cc 协议,如果我们的工作有侵犯到您的权益,请及时联系金年会app官方网。