深拷贝与浅拷贝

深拷贝与浅拷贝的概念

使用时需要导入 copy 模块

  1. 浅拷贝 (Shallow Copy)
    • 通过复制对象生成一个新的对象,但只复制对象的引用。
    • 对于容器对象(如列表、字典等),它只复制第一层的元素。
    • 对于嵌套的可变对象(如列表中的列表),它们仍然指向原对象的内存地址。
    • 在 Python 中,使用 copy.copy() 实现。
  2. 深拷贝 (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]]
---------------------------------------------------

解释:

  1. 修改原始列表的第一层:
    • 修改 original_list[0] 为 99
    • 结果: shallow_copied_list 和 deep_copied_list 不受影响,仍然为原来的 [1, 2, [3, 4]]
  2. 修改原始列表的嵌套列表:
    • 修改 original_list[2][0] 为 100
    • 结果: shallow_copied_list 的内部嵌套列表 shallow_copied_list[2] 也变为 [100, 4],这是因为浅拷贝共享了相同的嵌套列表,而 deep_copied_list 不受影响,仍为 [1, 2, [3, 4]]
  3. 修改浅拷贝的第一层:
    • 修改 shallow_copied_list[0] 为 88
    • 结果: original_list 仍为 [99, 2, [100, 4]],而 shallow_copied_list 变为 [88, 2, [100, 4]]
  4. 修改浅拷贝的嵌套列表:
    • 修改 shallow_copied_list[2][1] 为 200
    • 结果: original_list 变为 [99, 2, [100, 200]]shallow_copied_list 也变为 [88, 2, [100, 200]]。这是因为它们共享相同的内存引用。
  5. 修改深拷贝的嵌套列表:
    • 修改 deep_copied_list[2][1] 为 400
    • 结果: deep_copied_list 变为 [1, 2, [3, 400]],而 original_list 和 shallow_copied_list 没有受到影响,保持原值。

这段代码直观地证明了以下内容:

  1. 浅拷贝的共享特性:
    • 浅拷贝 (shallow_copied_list) 只拷贝对象的第一层引用,对于嵌套对象,其内部结构仍然引用原始对象中的相同内存地址。因此对嵌套对象的修改会影响原始对象和浅拷贝对象。
  2. 深拷贝的独立性:
    • 深拷贝 (deep_copied_list) 创建了一个全新的对象及其所有嵌套对象的独立拷贝。因此对深拷贝的任何修改都不会影响原始对象和浅拷贝。
  3. 操作的影响:
    • 修改原始对象的值不会影响浅拷贝和深拷贝的第一层,但是修改嵌套层的值会影响浅拷贝,但不会影响深拷贝。

使用场景

  • 浅拷贝的场景
    • 当你只需要一个新对象,但想保持对一些共享数据的访问时。
    • 性能要求高,入参对象较大,不需要复制嵌套对象。
  • 深拷贝的场景
    • 当你需要一个完全独立且不受原对象影响的副本时。
    • 处理复杂的嵌套数据结构时,如深层的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]]

共同点: 上述的前四种方式都是浅拷贝,即它们创建了一个新对象,但内部的元素是对原对象内元素的引用。

为什么一般只用浅拷贝?

  1. 性能: 浅拷贝一般比深拷贝有更好的性能,因为它只拷贝一层引用。
  2. 内存占用: 浅拷贝创建的新对象通常占用更少的内存,特别是当对象很大且嵌套较深时。
  3. 适用场景: 在许多情况下,我们操作的对象不会嵌套其他可变对象,所以使用浅拷贝就足够了。

发布者:LJH,转发请注明出处:https://www.ljh.cool/43137.html

Like (0)
LJH的头像LJH
Previous 2025年7月17日 上午9:09
Next 2025年7月20日 下午8:51

相关推荐

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注