在 Python 中涉及到对象拷贝主要有两个问题:
-
深拷贝和浅拷贝问题
-
自定义对象拷贝过程
-
深浅拷贝 {#title-0} ==================
深拷贝和浅拷贝的主要区别在于它们如何处理对象中的可变子对象。对于不可变类型不涉及到深浅拷贝问题。在 Python 中,只有字典、集合、列表属于可变类型。
1.1 浅拷贝 {#title-1}
创建一个新的对象,但只复制原对象的引用。这意味着原对象中的可变子对象(如列表、字典、集合等)不会被复制,而是共享相同的引用。修改任何子对象的内容都会影响到原对象和浅拷贝对象。
import copy
def ids(elemets):
print(id(elemets), end=' ')
for element in elemets:
print(id(element), end=' ')
print()
def test():
my_list1 = [[10, 20], {30, 40}, {'k1': 50, 'k2': 60}]
my_list2 = copy.copy(my_list1)
# [[10, 20], {40, 30}, {'k1': 50, 'k2': 60}]
# [[10, 20], {40, 30}, {'k1': 50, 'k2': 60}]
print(my_list1)
print(my_list2)
# 2271041540416 2271041542272 2271043720064 2271044354496
# 2271041874496 2271041542272 2271043720064 2271044354496
ids(my_list1)
ids(my_list2)
# 问题:任何一个列表发生改变,其他的列表也会发生改变
if __name__ == '__main__':
test()
程序的输出结果:
从输出结果来看,当我们使用 copy 函数或者切片对以一个包含可变类型的列表进行拷贝时,仅仅拷贝了子元素的引用,所以,当通过任意一个列表修改元素时,都会导致其他的元素发生变化。
1.2 深拷贝 {#title-2}
创建一个新的对象,并递归地复制原对象及其所有子对象。这意味着修改任何子对象的内容都不会影响原对象或深拷贝对象。对于嵌套数据结构,深拷贝确保每个层级的子对象都是独立的。
使用 copy 模块的 deepcopy 可以实现对象的深拷贝。
import copy
def ids(elemets):
print(id(elemets), end=' ')
for element in elemets:
print(id(element), end=' ')
print()
def test():
my_list1 = [[10, 20], {30, 40}, {'k1': 50, 'k2': 60}]
my_list2 = copy.deepcopy(my_list1)
# [[10, 20], {40, 30}, {'k1': 50, 'k2': 60}]
# [[10, 20], {40, 30}, {'k1': 50, 'k2': 60}]
print(my_list1)
print(my_list2)
# 21815361212736 1815361214592 1815363392384 1815364026816
# 1815361546816 1815364626432 1815364488992 1815364632000
ids(my_list1)
ids(my_list2)
if __name__ == '__main__':
test()
程序的执行结果:
对列表进行深拷贝之后,会对列表中的可变类型子元素进行递归拷贝。需要注意的是,对于不可变类型子元素,不用刻意区分深浅拷贝,因为即使是浅拷贝,一个列表修改了不可变类型子元素,也不会影响到另外一个列表。
- 拷贝协议 {#title-3} ==================
Python 通过拷贝协议支持自定义对象的拷贝过程,只需要在类内增加浅拷贝和深拷贝对应的魔术方法
__copy__
、__deepcopy__
两个函数。
假设:对象包含用户名和密码两个属性信息,该对象在拷贝时,不能进行密码拷贝(注意:默认会进行所有属性的拷贝),此时就可以使用自定义对象协议来实现。
import copy
class Demo:
def __init__(self, username, password):
self.username = username
self.password = password
def __repr__(self):
return 'username: %s, password: %s' % (self.username, self.password)
def __copy__(self):
new_demo = Demo(self.username, '')
return new_demo
def __deepcopy__(self, memodict={}):
new_demo = Demo(self.username, '')
return new_demo
def test():
demo1 = Demo('admin', '123456')
print(demo1)
# 这里会对所有属性的值进行拷贝(注意深浅拷贝问题)
# 假设: 该类型对象拷贝时,默认不进行密码拷贝,应该如何实现?
# 解决: 可以给 Demo 增加拷贝逻辑
demo2 = copy.copy(demo1)
print(demo2)
demo3 = copy.deepcopy(demo1)
print(demo3)
if __name__ == '__main__':
test()
深拷贝时,有个额外的参数 memodict
,它可以用来处理循环引用。如下例子,node1
引用 node2
,node2
引用 node1
, 在自定义深拷贝函数中,就陷入了无限递归,致使程序报错:
RecursionError: maximum recursion depth exceeded while calling a Python object
import copy
class Node:
def __init__(self, name):
self.name = name
self.next = None
def __deepcopy__(self, memodict={}):
new_node = Node(self.name)
# 递归拷贝其他节点
if self.next:
new_node.next = copy.deepcopy(self.next, memodict)
return new_node
def test():
node1 = Node('node1')
node2 = Node('node2')
node1.next = node2
node2.next = node1
new_node = copy.deepcopy(node1)
print(new_node)
if __name__ == '__main__':
test()
解决方法:我们可以将拷贝过的对象存储到 memodict
中,避免重复拷贝。如下代码所示:
def __deepcopy__(self, memodict={}):
# 如果当前节点已被拷贝,直接返回
if id(self) in memodict:
return memodict[id(self)]
new_node = Node(self.name)
# 当前节点 self 已经被拷贝,并记录下来
memodict[id(self)] = new_node
# 递归拷贝其他节点
if self.next:
new_node.next = copy.deepcopy(self.next, memodict)
return new_node