在Python里处理列表、字典、自定义类实例时,赋值、浅拷贝和深拷贝的内存语义不同。赋值只是新增引用,浅拷贝只复制外层,深拷贝递归复制所有层级。理解这三者,重点要盯住内部可变对象是否共享,以及修改后原对象是否被带动。
一、赋值:多个变量指向同一对象- a = [1, 2, [3, 4]]
- b = a
- b[0] = 100
- print(a)
复制代码 输出是[100, 2, [3, 4]]。这里a和b指向同一内存地址,改b等于改a。这个操作不是复制,排查数据被意外修改时,先看是否存在直接赋值共享。
二、浅拷贝:外层新对象,内部可变对象仍共享
浅拷贝的常见入口有list()、dict()、set()、切片lst[:]以及copy.copy()。它会给外层容器创建新对象,但内部可变元素只复制引用。- import copy
- a = [1, 2, [3, 4]]
- b = copy.copy(a)
- b[0] = 100
- b[2][0] = 300
- print(a)
复制代码 输出[1, 2, [300, 4]]。b[0]=100只改外层不可变元素,不影响a;b[2][0]=300改的是共享的内部列表,所以a也变。类似的切片和字典浅拷贝示例:- a = [1, [2, 3]]
- b = a[:]
- b[1][0] = 200
- print(a)
- a = {'k1': [1, 2]}
- b = copy.copy(a)
- b['k1'].append(3)
- print(a)
复制代码 分别输出[1, [200, 3]]和{'k1': [1, 2, 3]}。也就是说,浅拷贝后修改外层键值通常安全,修改嵌套列表、字典等内部可变对象会波及原对象。
三、深拷贝:递归复制所有层级
copy.deepcopy()会递归复制对象及其嵌套结构,生成完全独立的副本。新对象和原对象不共享内部可变对象。- import copy
- a = [1, 2, [3, 4]]
- b = copy.deepcopy(a)
- b[0] = 100
- b[2][0] = 300
- print(a)
复制代码 输出仍是[1, 2, [3, 4]]。嵌套字典同理:- a = {'k1': [1, 2], 'k2': {'inner': 3}}
- b = copy.deepcopy(a)
- b['k1'].append(3)
- b['k2']['inner'] = 300
- print(a)
复制代码 原字典保持{'k1': [1, 2], 'k2': {'inner': 3}}。深拷贝适合需要完全独立副本的场景,但递归复制越深、对象越大,开销越高。
四、三者区别归纳
赋值不创建新对象,只创建引用,内存完全共享,修改一个变量会影响所有引用,性能开销极低。
浅拷贝创建外层新对象,内部可变对象共享;修改外层通常不影响原对象,修改内部可变对象会影响原对象;开销中等。
深拷贝创建所有层级的新对象,内部无共享;修改副本不影响原对象;开销高。
实际选择时,核心判断不是“容器类型是什么”,而是“副本内部是否还有会被修改的可变对象”。
五、特殊对象与边界情况
纯不可变对象如数字、字符串、不含可变元素的元组,浅拷贝和深拷贝通常直接返回原对象,因为不可变对象无法被修改。但元组里嵌套列表这类结构不能只看外层类型,内部可变对象仍可能带来共享问题,需要结合copy行为实际验证。
自定义类默认拷贝行为由copy.copy()和copy.deepcopy()的默认逻辑处理;如果需要控制复制细节,可实现__copy__()和__deepcopy__()。下面示例展示默认浅拷贝下内部列表仍被共享:- import copy
- class MyClass:
- def __init__(self, value):
- self.value = value
- obj1 = MyClass([1, 2])
- obj2 = copy.copy(obj1)
- obj2.value.append(3)
- print(obj1.value)
复制代码 输出[1, 2, 3]。原因是浅拷贝只复制了实例外层,value列表仍指向同一对象。
循环引用也是深拷贝要处理的重点。深拷贝会自动处理循环引用,避免无限递归:- import copy
- a = [1]
- a.append(a)
- b = copy.deepcopy(a)
- print(b[1] is b)
复制代码 输出True,说明副本中的循环引用关系被正确保留。
六、应用场景
浅拷贝适合只改外层结构、内部对象不会被修改的场景,也适合性能敏感且只需一层副本的情况。例如复制配置字典后只替换server字段,内部options在确定不修改时可继续共享:- import copy
- config = {'server': 'localhost', 'options': {'timeout': 30}}
- new_config = copy.copy(config)
- new_config['server'] = 'remote'
复制代码 深拷贝适合需要完全独立副本的场景,例如游戏角色状态备份,避免备份数据与当前角色共享inventory:- import copy
- character = {'name': 'Hero', 'inventory': [{'item': 'sword', 'level': 5}]}
- backup = copy.deepcopy(character)
复制代码
七、性能对比与调试建议
深拷贝需要递归复制所有层级,性能通常显著低于浅拷贝。可用timeit对实际数据结构做对比:- import timeit
- import copy
- data = {'a': [1] * 1000}
- for _ in range(100):
- data = {'nested': data}
- print('浅拷贝耗时:', timeit.timeit(lambda: copy.copy(data), number=100))
- print('深拷贝耗时:', timeit.timeit(lambda: copy.deepcopy(data), number=100))
复制代码 这段代码只是演示测量方式,实际耗时取决于嵌套深度、对象数量和机器环境。若发现修改副本后原数据变化,优先检查是否用了赋值或浅拷贝;若深拷贝导致接口变慢,则要评估是否真的需要全量独立副本。
总结:赋值是共享引用;浅拷贝复制外层但共享内部可变对象;深拷贝递归复制所有层级。多数场景优先浅拷贝,只有确实需要完全独立副本时再用深拷贝;纯不可变对象可直接赋值;自定义类按需实现__copy__()和__deepcopy__()。围绕copy模块和嵌套可变对象设计数据结构,能减少意外修改和性能问题。 |