python

关注公众号 jb51net

关闭
首页 > 脚本专栏 > python > Python深拷贝与浅拷贝

一文带你深入了解Python中的深拷贝与浅拷贝数据

作者:detayun

在Python编程中,对象复制是一个常见但容易出错的操作,本文将系统讲解这两种拷贝方式的区别、应用场景及实现方法,感兴趣的小伙伴可以了解下

在Python编程中,对象复制是一个常见但容易出错的操作。许多开发者在处理可变对象(如列表、字典)时,常常会遇到"修改副本却影响了原对象"的困惑。这背后正是深拷贝和浅拷贝机制在起作用。本文将系统讲解这两种拷贝方式的区别、应用场景及实现方法。

一、基础概念:Python对象引用机制

在深入探讨拷贝前,必须理解Python的变量本质:

a = [1, 2, 3]
b = a  # 这并不是创建副本,而是创建新引用

b[0] = 99
print(a)  # 输出: [99, 2, 3]

这段代码揭示了Python的赋值本质:变量存储的是对象的引用,而非对象本身b = a只是让b指向与a相同的对象。

二、浅拷贝(Shallow Copy)

浅拷贝创建新对象,但不递归复制嵌套对象,而是复制嵌套对象的引用。

实现方式

示例1:列表的浅拷贝

import copy

original = [1, [2, 3], 4]
shallow_copy = copy.copy(original)

# 修改顶层元素
shallow_copy[0] = 100
print(original)    # [1, [2, 3], 4] - 不受影响

# 修改嵌套列表
shallow_copy[1][0] = 200
print(original)    # [1, [200, 3], 4] - 受影响!

示例2:字典的浅拷贝

original_dict = {'a': [1, 2], 'b': 3}
shallow_dict = original_dict.copy()  # 或 copy.copy(original_dict)

shallow_dict['a'][0] = 99
print(original_dict)  # {'a': [99, 2], 'b': 3} - 嵌套列表被修改

浅拷贝的内存结构

original: [obj1, [obj2, obj3], obj4]
           ^      ^
           |      |
shallow_copy: [obj1, [obj2, obj3], obj4]  # 嵌套列表是同一对象

三、深拷贝(Deep Copy)

深拷贝创建新对象,并递归复制所有嵌套对象,完全独立于原对象。

实现方式

唯一标准方法:copy.deepcopy()

import copy

original = [1, [2, 3], 4]
deep_copy = copy.deepcopy(original)

deep_copy[1][0] = 200
print(original)    # [1, [2, 3], 4] - 完全不受影响

深拷贝的内存结构

original: [obj1, [obj2, obj3], obj4]
           ^      ^
           |      |
deep_copy: [obj1, [obj5, obj6], obj4]  # 所有嵌套对象都是新创建的

四、关键区别对比

特性浅拷贝深拷贝
顶层对象新创建新创建
嵌套对象共享引用新创建副本
性能更快(不递归复制)较慢(需要递归处理)
内存占用较低较高
适用场景无嵌套或不需要独立嵌套对象时需要完全独立副本时

五、特殊情况处理

1. 不可变对象的拷贝

对于不可变类型(如数字、字符串、元组),浅拷贝和深拷贝效果相同:

import copy

t1 = (1, [2, 3])  # 注意:元组包含可变对象时行为特殊
t2 = copy.copy(t1)
t3 = copy.deepcopy(t1)

t2[1][0] = 99  # 仍然可以修改嵌套列表
print(t1)      # (1, [99, 3]) - 原元组受影响!

重要提醒:即使使用深拷贝,如果对象本身包含可变子对象(如元组中的列表),这些子对象仍然可以被修改!

2. 自定义对象的拷贝

对于自定义类,可以通过实现__copy__()__deepcopy__()方法控制拷贝行为:

class MyClass:
    def __init__(self, value):
        self.value = value
        self.nested = [1, 2, 3]
    
    def __copy__(self):
        print("执行浅拷贝")
        new_obj = MyClass(self.value)
        new_obj.nested = self.nested.copy()  # 手动浅拷贝嵌套列表
        return new_obj
    
    def __deepcopy__(self, memo):
        print("执行深拷贝")
        new_obj = MyClass(self.value)
        new_obj.nested = copy.deepcopy(self.nested, memo)  # 递归深拷贝
        return new_obj

obj = MyClass(10)
shallow = copy.copy(obj)
deep = copy.deepcopy(obj)

3. 循环引用的处理

深拷贝能正确处理对象间的循环引用:

a = [1, 2]
b = [a, 3]
a.append(b)  # 循环引用: a -> b -> a

c = copy.deepcopy(a)
print(c[0] is c[1][0])  # True - 保持引用关系
print(c is a)           # False - 新对象

六、实际应用场景

1. 何时使用浅拷贝

2. 何时使用深拷贝

3. 替代方案考虑

对于简单场景,可以考虑:

列表/字典推导式:创建部分独立副本

original = [1, [2, 3], 4]
partial_copy = [x if not isinstance(x, list) else x.copy() for x in original]

序列化/反序列化:通过JSON等格式实现深拷贝效果

import json
original = {'a': [1, 2], 'b': 3}
deep_copy = json.loads(json.dumps(original))

七、性能比较

测试不同拷贝方式的性能差异(对包含1000个元素的列表,其中每个元素是包含100个元素的列表):

import copy
import time

def create_nested_list(depth, size):
    if depth == 0:
        return 0
    return [create_nested_list(depth-1, size) for _ in range(size)]

big_list = create_nested_list(2, 1000)

# 测试浅拷贝
start = time.time()
for _ in range(100):
    shallow = copy.copy(big_list)
print(f"浅拷贝耗时: {(time.time()-start)*1000:.2f}ms")

# 测试深拷贝
start = time.time()
for _ in range(100):
    deep = copy.deepcopy(big_list)
print(f"深拷贝耗时: {(time.time()-start)*1000:.2f}ms")

典型结果

浅拷贝耗时: 15.32ms
深拷贝耗时: 1250.78ms

八、最佳实践总结

九、常见问题解答

Q1: 为什么list()构造函数创建的是浅拷贝?

A: 因为list()只复制顶层元素,对于可变子元素仍然共享引用。这是Python设计上的权衡,兼顾性能和常用场景需求。

Q2: 如何判断两个对象是否共享子对象?

A: 可以使用is操作符检查子对象引用是否相同:

a = [1, [2, 3]]
b = a.copy()
print(a[1] is b[1])  # True - 共享嵌套列表

Q3: 为什么深拷贝比浅拷贝慢这么多?

A: 深拷贝需要递归遍历整个对象图,为每个可变子对象创建新实例,而浅拷贝只需创建顶层对象并复制引用。

通过理解这些核心概念和实践技巧,您将能够更准确地控制Python中的对象复制行为,避免常见的编程陷阱,写出更健壮、高效的代码。

到此这篇关于一文带你深入了解Python中的深拷贝与浅拷贝数据的文章就介绍到这了,更多相关Python深拷贝与浅拷贝内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文