深拷贝与浅拷贝的概念
使用时需要导入 copy 模块
- 浅拷贝 (Shallow Copy):
- 通过复制对象生成一个新的对象,但只复制对象的引用。
- 对于容器对象(如列表、字典等),它只复制第一层的元素。
- 对于嵌套的可变对象(如列表中的列表),它们仍然指向原对象的内存地址。
- 在 Python 中,使用
copy.copy()
实现。
- 深拷贝 (Deep Copy):
- 创建一个完全独立的新对象,同时递归地复制原对象及其所有嵌套对象。
- 修改新对象的一部分不会影响到原对象。
- 在 Python 中,使用
copy.deepcopy()
实现。
如何使用深浅拷贝
浅拷贝示例
import copy
# 创建一个嵌套列表
original_list = [1, 2, [3, 4]]
# 使用浅拷贝
shallow_copied_list = copy.copy(original_list)
# 查看两者的id
print(id(original_list)) # 输出原列表的id
print(id(shallow_copied_list)) # 输出浅拷贝列表的id
# 修改嵌套列表
original_list[2][0] = 'changed'
print(original_list) # 输出: [1, 2, ['changed', 4]]
print(shallow_copied_list) # 输出: [1, 2, ['changed', 4]]

original_list
和shallow_copied_list
有不同的 ID,表示它们是不同的对象。- 当你改变
original_list
中嵌套列表的内容时,shallow_copied_list
中的相应内容也会变化,因为这两个列表共享同一个嵌套对象
深拷贝示例
import copy
# 创建一个嵌套列表
original_list = [1, 2, [3, 4]]
# 使用深拷贝
deep_copied_list = copy.deepcopy(original_list)
# 查看两者的id
print(id(original_list)) # 输出原列表的id
print(id(deep_copied_list)) # 输出深拷贝列表的id
# 修改嵌套列表
original_list[2][0] = 'changed'
print(original_list) # 输出: [1, 2, ['changed', 4]]
print(deep_copied_list) # 输出: [1, 2, [3, 4]]

original_list
和deep_copied_list
有不同的 ID,表示它们是不同的对象。- 当你改变
original_list
中嵌套列表的内容时,deep_copied_list
中的内容保持不变,因为深拷贝创建了嵌套对象的全新副本。
深浅拷贝修改不同层次上的元素
import copy
# 创建一个嵌套列表
original_list = [1, 2, [3, 4]]
# 创建浅拷贝
shallow_copied_list = copy.copy(original_list)
# 创建深拷贝
deep_copied_list = copy.deepcopy(original_list)
# 输出初始状态
print("Initial State:")
print("Original list:", original_list) # [1, 2, [3, 4]]
print("Shallow copied list:", shallow_copied_list) # [1, 2, [3, 4]]
print("Deep copied list:", deep_copied_list) # [1, 2, [3, 4]]
print("---------------------------------------------------")
# 修改原始列表的第一层
original_list[0] = 99
print("After modifying original_list[0]:")
print("Original list:", original_list) # [99, 2, [3, 4]]
print("Shallow copied list:", shallow_copied_list)
print("Deep copied list:", deep_copied_list)
print("---------------------------------------------------")
# 修改原始列表的嵌套列表(第二层)
original_list[2][0] = 100
print("After modifying original_list[2][0]:")
print("Original list:", original_list) # [99, 2, [100, 4]]
print("Shallow copied list:", shallow_copied_list)
print("Deep copied list:", deep_copied_list)
print("---------------------------------------------------")
# 修改浅拷贝的第一层
shallow_copied_list[0] = 88
print("After modifying shallow_copied_list[0]:")
print("Original list:", original_list)
print("Shallow copied list:", shallow_copied_list) # [88, 2, [100, 4]]
print("Deep copied list:", deep_copied_list)
print("---------------------------------------------------")
# 修改浅拷贝的嵌套列表(第二层)
shallow_copied_list[2][1] = 200
print("After modifying shallow_copied_list[2][1]:")
print("Original list:", original_list)
print("Shallow copied list:", shallow_copied_list)
print("Deep copied list:", deep_copied_list)
print("---------------------------------------------------")
# 修改深拷贝的嵌套列表(第二层)
deep_copied_list[2][1] = 400
print("After modifying deep_copied_list[2][1]:")
print("Original list:", original_list)
print("Shallow copied list:", shallow_copied_list)
print("Deep copied list:", deep_copied_list) # [1, 2, [3, 400]]
print("---------------------------------------------------")
输出:
Initial State:
Original list: [1, 2, [3, 4]]
Shallow copied list: [1, 2, [3, 4]]
Deep copied list: [1, 2, [3, 4]]
---------------------------------------------------
After modifying original_list[0]:
Original list: [99, 2, [3, 4]]
Shallow copied list: [1, 2, [3, 4]]
Deep copied list: [1, 2, [3, 4]]
---------------------------------------------------
After modifying original_list[2][0]:
Original list: [99, 2, [100, 4]]
Shallow copied list: [1, 2, [100, 4]]
Deep copied list: [1, 2, [3, 4]]
---------------------------------------------------
After modifying shallow_copied_list[0]:
Original list: [99, 2, [100, 4]]
Shallow copied list: [88, 2, [100, 4]]
Deep copied list: [1, 2, [3, 4]]
---------------------------------------------------
After modifying shallow_copied_list[2][1]:
Original list: [99, 2, [100, 200]]
Shallow copied list: [88, 2, [100, 200]]
Deep copied list: [1, 2, [3, 4]]
---------------------------------------------------
After modifying deep_copied_list[2][1]:
Original list: [99, 2, [100, 200]]
Shallow copied list: [88, 2, [100, 200]]
Deep copied list: [1, 2, [3, 400]]
---------------------------------------------------
解释:
- 修改原始列表的第一层:
- 修改
original_list[0]
为99
。 - 结果:
shallow_copied_list
和deep_copied_list
不受影响,仍然为原来的[1, 2, [3, 4]]
。
- 修改
- 修改原始列表的嵌套列表:
- 修改
original_list[2][0]
为100
。 - 结果:
shallow_copied_list
的内部嵌套列表shallow_copied_list[2]
也变为[100, 4]
,这是因为浅拷贝共享了相同的嵌套列表,而deep_copied_list
不受影响,仍为[1, 2, [3, 4]]
。
- 修改
- 修改浅拷贝的第一层:
- 修改
shallow_copied_list[0]
为88
。 - 结果:
original_list
仍为[99, 2, [100, 4]]
,而shallow_copied_list
变为[88, 2, [100, 4]]
。
- 修改
- 修改浅拷贝的嵌套列表:
- 修改
shallow_copied_list[2][1]
为200
。 - 结果:
original_list
变为[99, 2, [100, 200]]
,shallow_copied_list
也变为[88, 2, [100, 200]]
。这是因为它们共享相同的内存引用。
- 修改
- 修改深拷贝的嵌套列表:
- 修改
deep_copied_list[2][1]
为400
。 - 结果:
deep_copied_list
变为[1, 2, [3, 400]]
,而original_list
和shallow_copied_list
没有受到影响,保持原值。
- 修改
这段代码直观地证明了以下内容:
- 浅拷贝的共享特性:
- 浅拷贝 (
shallow_copied_list
) 只拷贝对象的第一层引用,对于嵌套对象,其内部结构仍然引用原始对象中的相同内存地址。因此对嵌套对象的修改会影响原始对象和浅拷贝对象。
- 浅拷贝 (
- 深拷贝的独立性:
- 深拷贝 (
deep_copied_list
) 创建了一个全新的对象及其所有嵌套对象的独立拷贝。因此对深拷贝的任何修改都不会影响原始对象和浅拷贝。
- 深拷贝 (
- 操作的影响:
- 修改原始对象的值不会影响浅拷贝和深拷贝的第一层,但是修改嵌套层的值会影响浅拷贝,但不会影响深拷贝。
使用场景
- 浅拷贝的场景:
- 当你只需要一个新对象,但想保持对一些共享数据的访问时。
- 性能要求高,入参对象较大,不需要复制嵌套对象。
- 深拷贝的场景:
- 当你需要一个完全独立且不受原对象影响的副本时。
- 处理复杂的嵌套数据结构时,如深层的JSON对象或自定义的数据结构。
自定义对象的拷贝
如果我们有一个自定义类,我们也可以实现深拷贝和浅拷贝。下面是一个示例:
import copy
class MyClass:
def __init__(self, value):
self.value = value
self.nested_list = [1, 2, [3, 4]]
def __repr__(self):
return f"MyClass(value={self.value}, nested_list={self.nested_list})"
# 创建对象
original_object = MyClass(10)
# 浅拷贝
shallow_copied_object = copy.copy(original_object)
# 深拷贝
deep_copied_object = copy.deepcopy(original_object)
# 修改嵌套列表
original_object.nested_list[2][0] = 'changed'
# 输出三个对象
print("Original Object:", original_object) # 输出: MyClass(value=10, nested_list=[1, 2, ['changed', 4]])
print("Shallow Copied Object:", shallow_copied_object) # 输出: MyClass(value=10, nested_list=[1, 2, ['changed', 4]])
print("Deep Copied Object:", deep_copied_object) # 输出: MyClass(value=10, nested_list=[1, 2, [3, 4]])
循环引用的处理
深拷贝能够安全地处理对象间的循环引用,而浅拷贝则不具备这样的能力。以下是演示的示例:
import copy
class Node:
def __init__(self, value):
self.value = value
self.next = None
# 创建节点
node1 = Node(1)
node2 = Node(2)
node1.next = node2
node2.next = node1 # 形成循环引用
# 浅拷贝
shallow_copied_node = copy.copy(node1)
# 深拷贝
deep_copied_node = copy.deepcopy(node1)
# 修改 original node1
node1.value = 'changed'
# 输出结果
print("Original Node:", node1.value, "->", node1.next.value) # 输出: changed -> 2
print("Shallow Copied Node:", shallow_copied_node.value, "->", shallow_copied_node.next.value) # 输出: changed -> 2
print("Deep Copied Node:", deep_copied_node.value, "->", deep_copied_node.next.value) # 输出: 1 -> 2
注意事项和潜在问题
- 性能:
- 深拷贝的性能成本较高,尤其对于大对象,因为它需要递归地复制所有嵌套对象。
- 浅拷贝相对较快,因为它只涉及到对引用的复制。
- 不可变对象:对于不可变对象(如字符串、元组),浅拷贝和深拷贝的效果是一样的,因为它们无法被修改。
import copy
# 创建一个字符串
original_string = "Hello, World!"
# 浅拷贝
shallow_copied_string = copy.copy(original_string)
# 深拷贝
deep_copied_string = copy.deepcopy(original_string)
# 由于字符串是不可变的,修改其中一个也不会影响另一个
original_string += " Goodbye!"
print("Original String:", original_string) # 输出: Original String: Hello, World! Goodbye!
print("Shallow Copied String:", shallow_copied_string) # 输出: Shallow Copied String: Hello, World!
print("Deep Copied String:", deep_copied_string) # 输出: Deep Copied String: Hello, World!
当执行 original_string += " Goodbye!"
时,Python 实际上是创建了一个新的字符串对象,即 Hello, World! Goodbye!
,并将其赋值给 original_string
。这样,shallow_copied_string
和 deep_copied_string
保持不变,因为它们仍然指向原来的 Hello, World!
。
import copy
# 创建一个元组
original_tuple = (1, 2, (3, 4))
# 浅拷贝
shallow_copied_tuple = copy.copy(original_tuple)
# 深拷贝
deep_copied_tuple = copy.deepcopy(original_tuple)
# 修改元组是不可变的,所以不能直接改变其中的元素
# 但可以创建新的元组来赋值
new_tuple = original_tuple + (5,)
print("Original Tuple:", original_tuple) # 输出: Original Tuple: (1, 2, (3, 4))
print("Shallow Copied Tuple:", shallow_copied_tuple) # 输出: Shallow Copied Tuple: (1, 2, (3, 4))
print("Deep Copied Tuple:", deep_copied_tuple) # 输出: Deep Copied Tuple: (1, 2, (3, 4))
print("New Tuple:", new_tuple) # 输出: New Tuple: (1, 2, (3, 4), 5)
- 自定义对象的拷贝:
- 如果你有自定义对象,使用深拷贝可能需要重写
__deepcopy__
方法,以控制如何拷贝该对象的属性。
- 如果你有自定义对象,使用深拷贝可能需要重写
class MyClass:
def __init__(self, value):
self.value = value
def __deepcopy__(self, memo):
# 自定义深拷贝逻辑
new_obj = MyClass(copy.deepcopy(self.value, memo))
return new_obj
- 循环引用:
- 深拷贝处理循环引用时会比较安全,而浅拷贝可能会导致多个引用指向同一个对象。
deepcopy
会在内部使用一个 memo 字典来避免无限递归,确保每个对象只被复制一次。
常见的四种拷贝方式及其区别
1. 使用 copy
模块的 copy()
方法(浅拷贝)
import copy
original_list = [1, 2, [3, 4]]
shallow_copied_list = copy.copy(original_list)
shallow_copied_list[2][0] = 99
print("Original list after modifying shallow copy:", original_list) # [1, 2, [99, 4]]
print("Shallow copied list:", shallow_copied_list) # [1, 2, [99, 4]]
2. 使用对象本身的 copy()
方法(若对象提供)
class CustomList:
def __init__(self, data):
self.data = data
def copy(self):
return CustomList(self.data.copy())
original_custom_list = CustomList([1, 2, [3, 4]])
copied_custom_list = original_custom_list.copy()
copied_custom_list.data[2][0] = 99
print("Original custom list after modifying copied list:", original_custom_list.data) # [1, 2, [99, 4]]
print("Copied custom list:", copied_custom_list.data) # [1, 2, [99, 4]]
3. 利用工厂方法拷贝
一般来说,工厂方法是指通过构造器或者其他类方法实例化一个对象,但对于容器对象,可以直接调用构造函数。对于列表、字典等容器,这种方式也通常是浅拷贝。
original_list = [1, 2, [3, 4]]
factory_copied_list = list(original_list)
factory_copied_list[2][0] = 99
print("Original list after modifying factory copied list:", original_list) # [1, 2, [99, 4]]
print("Factory copied list:", factory_copied_list) # [1, 2, [99, 4]]
4. 利用切片方式拷贝
切片方式以列表为例,也是浅拷贝。
original_list = [1, 2, [3, 4]]
sliced_copied_list = original_list[:]
sliced_copied_list[2][0] = 99
print("Original list after modifying sliced copied list:", original_list) # [1, 2, [99, 4]]
print("Sliced copied list:", sliced_copied_list) # [1, 2, [99, 4]]
共同点: 上述的前四种方式都是浅拷贝,即它们创建了一个新对象,但内部的元素是对原对象内元素的引用。
为什么一般只用浅拷贝?
- 性能: 浅拷贝一般比深拷贝有更好的性能,因为它只拷贝一层引用。
- 内存占用: 浅拷贝创建的新对象通常占用更少的内存,特别是当对象很大且嵌套较深时。
- 适用场景: 在许多情况下,我们操作的对象不会嵌套其他可变对象,所以使用浅拷贝就足够了。
发布者:LJH,转发请注明出处:https://www.ljh.cool/43137.html