一文详解Python赋值传递中的深浅拷贝问题
作者:看风起长林
还在为Python赋值、浅拷贝和深拷贝的区别头疼吗,本文用通俗例子讲透三者的核心差异,帮你理解可变对象共享引用的坑,掌握copy.copy和copy.deepcopy的适用场景,从此轻松应对面试和开发中的数据复制难题,需要的朋友可以参考下
一、开篇:赋值、浅拷贝、深拷贝——三个容易混淆的概念
上一篇文章我们搞懂了可变类型与不可变类型。但那只是故事的一半——知道"可变对象会被共享修改"还不够,你还得知道如何正确地"复制"数据,在需要的时候切断这种共享。
来看一个在实际项目中经常遇到的场景:
# 你有一个模板配置
template_config = {
'host': 'localhost',
'port': 8080,
'database': {
'host': 'db.local',
'port': 5432,
'pool': {'min': 1, 'max': 10}
},
'cache_servers': ['redis://cache1:6379', 'redis://cache2:6379']
}
# 你需要为三个不同的环境创建配置
dev_config = template_config # 直接赋值——对吗?
dev_config['host'] = 'dev.server.com'
dev_config['database']['host'] = 'dev-db.local'
print(template_config['host']) # dev.server.com —— 被修改了!
print(template_config['database']['host']) # dev-db.local —— 也被修改了!
上面的输出你能准确预测吗?如果你脱口而出"还是原来的值",那就中招了。直接赋值没有创建任何副本——dev_config和template_config指向的是同一个字典对象。
今天我们要彻底搞懂Python中的三种"数据传递"方式:赋值(Assignment)、浅拷贝(Shallow Copy)和深拷贝(Deep Copy)。这三者之间的区别,是Python面试中最高频的考点之一,也是实际开发中最容易踩坑的地方。
二、基础:变量不是盒子,是标签
2.1 重新理解赋值
在深入拷贝之前,必须先建立正确的Python变量模型:
# Python的变量不是"装着数据的盒子"
# 而是"贴在对象上的标签"
# 赋值 = 贴标签,不创建新对象
a = [1, 2, 3]
b = a # 只是给同一个列表多贴了一个标签
print(f'a is b: {a is b}') # True——同一个对象
print(f'id(a): {id(a)}, id(b): {id(b)}') # 相同
# 修改a就修改了b(因为它们指向同一个对象)
a.append(4)
print(f'a: {a}') # [1, 2, 3, 4]
print(f'b: {b}') # [1, 2, 3, 4]
# 图示:
# a = [1,2,3] : a → [1, 2, 3]
# b = a : a → [1, 2, 3] ← b
# a.append(4) : a → [1, 2, 3, 4] ← b
2.2 对不可变类型,赋值是"安全"的
# 不可变类型没有"原地修改"的问题
x = 100
y = x # y指向同一个100对象
x = 200 # x指向新的200对象,y还指向100
print(f'x: {x}, y: {y}') # x: 200, y: 100
# 初始时 x → 100 ← y
# 重新赋值后 x → 200 100 ← y
# 没有"共享修改"的问题,因为根本没修改——只是让x指向了新对象
这引出了一个关键观点:对于不可变类型,赋值通常就是"够用的"。浅拷贝和深拷贝主要是为了解决可变类型的共享引用问题。
三、浅拷贝(Shallow Copy)
3.1 什么是浅拷贝
浅拷贝创建一个新的容器对象,但容器中的元素仍然是对原对象元素的引用。
用人话说:你复制了一个列表的"壳",但壳里面的东西还是原来的。
import copy
# 原列表
original = [1, 2, [10, 20]]
print(f'原始: {original}')
# 浅拷贝
shallow = copy.copy(original)
print(f'浅拷贝: {shallow}')
print(f'是同一个对象吗: {original is shallow}') # False——容器本身是新的
# 但是!嵌套的可变元素还是共享的
print(f'内部列表是同一个吗: {original[2] is shallow[2]}') # True!
# 图解:
# original → [1, 2, ●]——→ [10, 20]
# shallow → [1, 2, ●]——→ 同上(同一个[10, 20])
# 所以修改嵌套元素会同时影响两者
original[2].append(30)
print(f'原始: {original}') # [1, 2, [10, 20, 30]]
print(f'浅拷贝: {shallow}') # [1, 2, [10, 20, 30]]——也被影响了!
# 但是修改顶层元素不会相互影响
original[0] = 999
print(f'原始: {original}') # [999, 2, [10, 20, 30]]
print(f'浅拷贝: {shallow}') # [1, 2, [10, 20, 30]]——不受影响
3.2 创建浅拷贝的N种方式
import copy
original = [1, 2, 3, 4, 5]
# 方式一:copy.copy()——最显式
shallow1 = copy.copy(original)
# 方式二:list()构造函数
shallow2 = list(original)
# 方式三:切片 [:]
shallow3 = original[:]
# 方式四:copy()方法(列表、字典、集合都支持)
shallow4 = original.copy()
# 方式五:列表推导式
shallow5 = [x for x in original]
# 验证都是浅拷贝
for name, s in [('copy.copy', shallow1), ('list()', shallow2),
('[:]', shallow3), ('.copy()', shallow4), ('推导式', shallow5)]:
print(f'{name}: 不同对象={original is not s}, 元素相同={original == s}')
# 注意:copy.copy()是通用方法,而.copy()是类型的实例方法
# 对于不知道类型的情况(如函数参数),使用copy.copy()更稳妥
3.3 字典和集合的浅拷贝
import copy
# 字典的浅拷贝
original_dict = {
'name': '小明',
'scores': [85, 92, 78], # 可变元素
'profile': {'age': 25, 'city': '北京'} # 嵌套可变元素
}
# 创建浅拷贝的各种方式
d1 = original_dict.copy() # dict的copy方法
d2 = dict(original_dict) # dict()构造函数
d3 = {**original_dict} # 字典解包(Python 3.5+)
d4 = copy.copy(original_dict) # copy模块
# 验证:容器是新的,嵌套可变元素是共享的
print(f'd1是新的: {d1 is not original_dict}')
print(f'scores是共享的: {d1["scores"] is original_dict["scores"]}')
print(f'profile是共享的: {d1["profile"] is original_dict["profile"]}')
# 修改嵌套元素——会同时影响
original_dict['scores'].append(100)
print(f'原始scores: {original_dict["scores"]}') # [85, 92, 78, 100]
print(f'拷贝scores: {d1["scores"]}') # [85, 92, 78, 100]——也变了!
# 集合的浅拷贝
original_set = {1, 2, frozenset([3, 4])}
s1 = original_set.copy() # set的copy方法
s2 = set(original_set) # set()构造函数
s3 = copy.copy(original_set) # copy模块
print(f'\ns1是新的: {s1 is not original_set}') # True
print(f's1 == original_set: {s1 == original_set}') # True
3.4 浅拷贝的"浅"到底体现在哪里
# 用一个三层嵌套结构来直观展示
original = [
[1, 2], # 索引0:第一层嵌套
[3, [4, 5]], # 索引1:第二层嵌套
{'a': [6, 7]} # 索引2:第二层嵌套(字典里的列表)
]
import copy
shallow = copy.copy(original)
# 第一层:容器本身——不共享
print(f'顶层不同对象: {original is not shallow}') # True
# 第二层:第一层的元素——共享!
print(f'original[0]共享: {original[0] is shallow[0]}') # True
print(f'original[1]共享: {original[1] is shallow[1]}') # True
print(f'original[2]共享: {original[2] is shallow[2]}') # True
# 第三层:第二层中的元素——也是共享的
print(f'original[1][1]共享: {original[1][1] is shallow[1][1]}') # True
print(f'original[2]["a"]共享: {original[2]["a"] is shallow[2]["a"]}') # True
# 修改影响范围示例
# 修改第一层——互不影响
original[0] = 'changed'
print(f'改顶层后: original[0]={"changed"}, shallow[0]={shallow[0]}')
# 原来的[1,2]还在shallow[0]中
# 修改第二层——互相影响!
original[1].append(999)
print(f'改第二层后: original[1]={original[1]}, shallow[1]={shallow[1]}')
# 两边都看到了999
# ⚠️ 关键结论:浅拷贝只拷贝一层
# "一层"指的是容器本身这一层
# 容器内的所有元素(不管嵌套多少层)都还是共享的
四、深拷贝(Deep Copy)
4.1 什么是深拷贝
深拷贝递归地复制所有层级的对象。原对象和拷贝对象之间没有任何共享的子对象。
import copy
original = [1, 2, [10, 20, [100, 200]]]
deep = copy.deepcopy(original)
# 验证:每一层都是独立的新对象
print(f'顶层不同: {original is not deep}') # True
print(f'original[2]不同: {original[2] is not deep[2]}') # True
print(f'original[2][2]不同: {original[2][2] is not deep[2][2]}') # True
# 现在怎么修改都不会影响对方
original[2].append(30)
original[2][2].append(300)
print(f'修改后 original: {original}') # [1, 2, [10, 20, [100, 200, 300], 30]]
print(f'修改后 deep: {deep}') # [1, 2, [10, 20, [100, 200]]]——完全不受影响
# 图解:
# original → [1, 2, ●] → [10, 20, ●] → [100, 200]
# deep → [1, 2, ▲] → [10, 20, ▲] → [100, 200]
# ●和▲指向不同的对象——每一层都是独立的副本
4.2 深拷贝的递归行为
import copy
# 复杂嵌套结构
class Person:
def __init__(self, name, friends=None):
self.name = name
self.friends = friends if friends is not None else []
def __repr__(self):
return f'Person({self.name}, friends={[f.name for f in self.friends]})'
# 构建一个小的社交网络
alice = Person('Alice')
bob = Person('Bob')
charlie = Person('Charlie')
alice.friends = [bob, charlie]
bob.friends = [alice]
charlie.friends = [alice, bob]
# 深拷贝Alice
alice_deep = copy.deepcopy(alice)
print('=== 深拷贝后 ===')
print(f'alice is alice_deep: {alice is alice_deep}') # False
print(f'alice.name: {alice.name}, alice_deep.name: {alice_deep.name}')
# 深拷贝连friends列表中的Person对象也复制了
print(f'alice.friends[0] is alice_deep.friends[0]: '
f'{alice.friends[0] is alice_deep.friends[0]}') # False!
# 验证:修改deep拷贝不影响原对象
alice_deep.name = 'Alice_copy'
alice_deep.friends[0].name = 'Bob_copy'
print(f'\nalice.name: {alice.name}') # Alice——不受影响
print(f'bob.name: {bob.name}') # Bob——不受影响
print(f'alice_deep.friends[0].name: {alice_deep.friends[0].name}') # Bob_copy
4.3 深拷贝处理循环引用
import copy
# 循环引用:a包含b,b包含a
a = []
b = [a]
a.append(b)
# 这形成了一个循环:a → [b] → [a] → [b] → ...
print(f'a: {a}')
# 输出:a: [[[...]]]
# 深拷贝处理循环引用——不会无限递归
a_deep = copy.deepcopy(a)
print(f'a_deep: {a_deep}')
# 拷贝后的循环引用是独立的
print(f'a is a_deep: {a is a_deep}') # False
# 但拷贝后的结构保持了循环引用
print(f'a_deep[0][0] is a_deep: {a_deep[0][0] is a_deep}') # True
# 💡 deepcopy内部维护了一个memo字典
# 记录已经拷贝过的对象,遇到循环引用就直接复用
# 这就是为什么deepcopy不会陷入无限递归
五、赋值 vs 浅拷贝 vs 深拷贝——全面对比
5.1 三者的核心区别
import copy
original = [1, 2, [3, 4]]
# 1. 赋值——不同变量,同一对象
assigned = original
# 2. 浅拷贝——不同对象,但共享内部可变元素
shallow = copy.copy(original) # 或 original.copy()、original[:]
# 3. 深拷贝——完全独立的对象
deep = copy.deepcopy(original)
print('=== 初始状态 ===')
print(f'original: {original}')
print(f'assigned: {assigned}')
print(f'shallow: {shallow}')
print(f'deep: {deep}')
print('\n=== 修改 original[0](顶层元素) ===')
original[0] = 999
print(f'original: {original}') # [999, 2, [3, 4]]
print(f'assigned: {assigned}') # [999, 2, [3, 4]] ← 受影响!
print(f'shallow: {shallow}') # [1, 2, [3, 4]] ← 不受影响
print(f'deep: {deep}') # [1, 2, [3, 4]] ← 不受影响
print('\n=== 修改 original[2](嵌套可变元素) ===')
original[2].append(5)
print(f'original: {original}') # [999, 2, [3, 4, 5]]
print(f'assigned: {assigned}') # [999, 2, [3, 4, 5]] ← 受影响!
print(f'shallow: {shallow}') # [1, 2, [3, 4, 5]] ← 受影响!
print(f'deep: {deep}') # [1, 2, [3, 4]] ← 不受影响!
5.2 一图看懂
# 初始状态 original = [1, 2, [3, 4]] # 内存模型: # # 赋值 (assigned = original): # original → [1, 2, ●] → [3, 4] # assigned ↗ ↗ (共享一切) # # 浅拷贝 (shallow = copy.copy(original)): # original → [1, 2, ●] → [3, 4] # shallow → [1, 2, ●] ↗ (共享嵌套) # # 深拷贝 (deep = copy.deepcopy(original)): # original → [1, 2, ●] → [3, 4] # deep → [1, 2, ▲] → [3, 4] (完全独立)
5.3 速查对比表
| 操作 | 创建新容器 | 复制嵌套可变对象 | 修改顶层互不影响 | 修改嵌套互不影响 |
|---|---|---|---|---|
赋值 b = a | ❌ | ❌ | ❌ | ❌ |
浅拷贝 copy.copy(a) | ✅ | ❌ | ✅ | ❌ |
深拷贝 copy.deepcopy(a) | ✅ | ✅ | ✅ | ✅ |
六、各种数据结构的拷贝行为
6.1 列表、元组、字典、集合的拷贝
import copy
# === 列表 ===
lst = [1, [2, 3]]
lst_shallow = lst.copy() # 浅拷贝
lst_deep = copy.deepcopy(lst) # 深拷贝
# === 字典 ===
d = {'a': 1, 'b': [2, 3]}
d_shallow = d.copy() # 浅拷贝
d_deep = copy.deepcopy(d) # 深拷贝
# === 集合 ===
s = {1, frozenset([2, 3])}
s_shallow = s.copy() # 浅拷贝
s_deep = copy.deepcopy(s) # 深拷贝
# === 元组 ===
# ⚠️ 注意:元组是不可变的
t = (1, [2, 3])
# 浅拷贝元组——因为元组不可变,浅拷贝通常返回元组本身
t_shallow = copy.copy(t)
print(f't is t_shallow: {t is t_shallow}') # True!返回了同一个对象
# 但是深拷贝会创建新元组——因为内部有可变元素需要递归拷贝
t_deep = copy.deepcopy(t)
print(f't is t_deep: {t is t_deep}') # False
print(f't[1] is t_deep[1]: {t[1] is t_deep[1]}') # False——内部列表也被拷贝了
6.2 自定义对象的拷贝行为
import copy
class MyClass:
def __init__(self, value):
self.value = value
obj = MyClass([1, 2, 3])
obj_shallow = copy.copy(obj)
obj_deep = copy.deepcopy(obj)
print(f'obj is shallow_copy: {obj is obj_shallow}') # False——新对象
print(f'obj.value is shallow_copy.value: {obj.value is obj_shallow.value}') # True——内部属性共享
print(f'obj.value is deep_copy.value: {obj.value is obj_deep.value}') # False——内部属性独立
# 修改内部属性验证
obj.value.append(999)
print(f'修改后 obj.value: {obj.value}')
print(f'修改后 shallow.value: {obj_shallow.value}') # 也变了
print(f'修改后 deep.value: {obj_deep.value}') # 没变
七、掌控拷贝行为:copy__和__deepcopy
7.1 自定义浅拷贝行为
import copy
class Config:
def __init__(self, data):
self.data = data
self.created_at = "2025-01-01"
def __copy__(self):
"""自定义浅拷贝行为"""
print(' __copy__ 被调用')
new = Config(self.data.copy()) # 浅拷贝了data
new.created_at = self.created_at
return new
def __repr__(self):
return f'Config(data={self.data}, created_at={self.created_at})'
config = Config({'host': 'localhost', 'port': 8080})
print(f'原始: {config}')
# 浅拷贝
config_copy = copy.copy(config)
print(f'拷贝: {config_copy}')
print(f'是否同一对象: {config is config_copy}') # False
print(f'data是否共享: {config.data is config_copy.data}') # False(因为__copy__里做了.copy())
7.2 自定义深拷贝行为
import copy
class DatabaseConnection:
"""模拟一个数据库连接——不能被深拷贝"""
def __init__(self, connection_string):
self.connection_string = connection_string
self._connected = True
def __deepcopy__(self, memo):
"""
自定义深拷贝行为。
对于数据库连接,我们不想复制连接——而是共享同一个连接。
"""
print(' __deepcopy__ 被调用——共享连接')
return self # 直接返回自身,不创建副本
def __repr__(self):
return f'DB(conn={self.connection_string[:15]}...)'
class AppConfig:
def __init__(self, name, db_connection, settings):
self.name = name
self.db = db_connection # 数据库连接(不希望被深拷贝)
self.settings = settings # 普通配置(需要被深拷贝)
def __deepcopy__(self, memo):
"""自定义深拷贝——只深拷贝settings,共享db连接"""
print(' AppConfig.__deepcopy__ 被调用')
new = AppConfig(
name=self.name,
db_connection=self.db, # 数据库连接——共享!
settings=copy.deepcopy(self.settings, memo) # 深拷贝settings
)
return new
# 使用示例
db = DatabaseConnection('postgresql://user:pass@localhost:5432/mydb')
config = AppConfig('MyApp', db, {
'debug': False,
'cache': {'host': 'redis://localhost', 'port': 6379}
})
print('原始:')
print(f' {config}')
print('\n深拷贝:')
config_deep = copy.deepcopy(config)
print(f' {config_deep}')
print(f'\n验证:')
print(f' config.db is config_deep.db: {config.db is config_deep.db}') # True——共享
print(f' config.settings is config_deep.settings: {config.settings is config_deep.settings}') # False——独立
八、实战场景
8.1 配置模板与环境配置
import copy
class ConfigManager:
"""管理多环境配置——深拷贝的经典应用场景"""
def __init__(self, template):
self._template = template
def create_config(self, **overrides):
"""基于模板创建新配置,并用overrides覆盖"""
config = copy.deepcopy(self._template)
for key, value in overrides.items():
config[key] = value
return config
# 模板配置(包含嵌套结构)
template = {
'host': 'localhost',
'port': 8080,
'database': {
'host': 'db.local',
'port': 5432,
'pool': {'min': 1, 'max': 10}
},
'cache_servers': ['redis://cache1:6379'],
'features': {'dark_mode': False, 'beta': False}
}
manager = ConfigManager(template)
# 创建三个环境的配置
dev = manager.create_config(
host='dev.myapp.com',
features={'dark_mode': True, 'beta': True}
)
staging = manager.create_config(
host='staging.myapp.com',
port=8443,
)
prod = manager.create_config(
host='myapp.com',
port=443,
database={'host': 'prod-db.myapp.com', 'pool': {'min': 5, 'max': 100}},
cache_servers=['redis://cache1:6379', 'redis://cache2:6379', 'redis://cache3:6379'],
)
# 验证:修改一个环境不影响其他
dev['database']['host'] = 'dev-db.myapp.com'
dev['cache_servers'].append('redis://dev-cache:6379')
print('开发环境 database.host:', dev['database']['host'])
print('生产环境 database.host:', prod['database']['host']) # 不受影响
print('开发环境 cache_servers:', dev['cache_servers'])
print('模板 cache_servers:', template['cache_servers']) # 模板也不受影响
8.2 撤销/重做(Undo/Redo)系统
import copy
class UndoableList:
"""带撤销功能的列表——用深拷贝保存状态快照"""
def __init__(self, initial=None):
self._data = list(initial) if initial else []
self._undo_stack = []
self._redo_stack = []
def _save_state(self):
"""保存当前状态到撤销栈"""
self._undo_stack.append(copy.deepcopy(self._data))
self._redo_stack.clear() # 新操作后,重做栈失效
def append(self, item):
self._save_state()
self._data.append(item)
def insert(self, index, item):
self._save_state()
self._data.insert(index, item)
def remove(self, item):
self._save_state()
self._data.remove(item)
def pop(self, index=-1):
self._save_state()
return self._data.pop(index)
def undo(self):
"""撤销上一步操作"""
if not self._undo_stack:
print('没有可撤销的操作')
return False
self._redo_stack.append(copy.deepcopy(self._data))
self._data = self._undo_stack.pop()
return True
def redo(self):
"""重做被撤销的操作"""
if not self._redo_stack:
print('没有可重做的操作')
return False
self._undo_stack.append(copy.deepcopy(self._data))
self._data = self._redo_stack.pop()
return True
def __repr__(self):
return str(self._data)
# 使用示例
ul = UndoableList([1, 2, 3])
print(f'初始: {ul}')
ul.append(4)
print(f'append(4): {ul}')
ul.append(5)
print(f'append(5): {ul}')
ul.undo()
print(f'undo: {ul}') # [1, 2, 3, 4]——撤销了append(5)
ul.undo()
print(f'undo: {ul}') # [1, 2, 3]——撤销了append(4)
ul.redo()
print(f'redo: {ul}') # [1, 2, 3, 4]——重做了append(4)
8.3 游戏存档系统
import copy
class GameState:
"""游戏状态管理——用深拷贝实现存档功能"""
def __init__(self):
self.player = {
'name': 'Hero',
'hp': 100,
'mp': 50,
'position': [0, 0],
'inventory': [
{'name': 'Sword', 'damage': 10, 'enchantments': ['fire']},
{'name': 'Potion', 'effect': 'heal', 'amount': 50},
],
'skills': {
'slash': {'level': 1, 'damage': 25},
'fireball': {'level': 2, 'damage': 40},
}
}
self.enemies = [
{'name': 'Goblin', 'hp': 30, 'position': [5, 3]},
{'name': 'Orc', 'hp': 80, 'position': [10, 8]},
]
def save(self):
"""创建当前状态的深拷贝作为存档"""
return copy.deepcopy({
'player': self.player,
'enemies': self.enemies,
})
def load(self, save_data):
"""从存档恢复状态"""
self.player = copy.deepcopy(save_data['player'])
self.enemies = copy.deepcopy(save_data['enemies'])
# 使用示例
game = GameState()
print(f"初始HP: {game.player['hp']}, 位置: {game.player['position']}")
# 保存进度
save_point = game.save()
# 游戏过程中修改状态
game.player['hp'] -= 30
game.player['position'] = [3, 4]
game.player['inventory'].append({'name': 'Shield', 'defense': 15})
game.enemies[0]['hp'] -= 25
print(f"战斗后HP: {game.player['hp']}, 位置: {game.player['position']}")
# 从存档恢复
game.load(save_point)
print(f"读档后HP: {game.player['hp']}, 位置: {game.player['position']}")
# HP恢复到100,位置恢复到[0, 0]
九、切片赋值:一种特殊的拷贝相关操作
# 切片赋值和拷贝有一些微妙的关系
# 替换整个列表内容——不创建新列表对象
original = [1, 2, 3]
print(f'替换前 id: {id(original)}')
original[:] = [4, 5, 6] # 原地替换内容
print(f'替换后 id: {id(original)}') # id不变
print(f'内容: {original}') # [4, 5, 6]
# 对比:original = [4,5,6] 会创建新对象
original2 = [1, 2, 3]
print(f'\n赋值前 id: {id(original2)}')
original2 = [4, 5, 6] # 指向新列表
print(f'赋值后 id: {id(original2)}') # id变了
# 这在实际开发中有什么用?
# 当你需要清空或替换一个共享列表的内容时:
def update_in_place(shared_list):
"""修改共享列表的内容而不改变其身份"""
shared_list[:] = [x * 2 for x in shared_list]
data = [1, 2, 3]
ref_to_data = data # 另一个引用
update_in_place(data)
print(f'data: {data}') # [2, 4, 6]
print(f'ref_to_data: {ref_to_data}') # [2, 4, 6]——能看到修改
# ⚠️ 但如果用 data = [x*2 for x in data]:
data2 = [1, 2, 3]
ref2 = data2
data2 = [x * 2 for x in data2] # data2指向了新列表!
print(f'data2: {data2}') # [2, 4, 6]
print(f'ref2: {ref2}') # [1, 2, 3]——还是旧的!
十、常见陷阱与最佳实践
10.1 陷阱一:* 操作符的浅拷贝特性
# ❌ 经典陷阱:[[]] * n
matrix = [[0] * 3] * 3
print(matrix) # [[0, 0, 0], [0, 0, 0], [0, 0, 0]]
matrix[0][0] = 999
print(matrix) # [[999, 0, 0], [999, 0, 0], [999, 0, 0]]
# 三行都变了!因为每一行都是对同一个列表的引用
# ✅ 正确方式
matrix = [[0] * 3 for _ in range(3)]
matrix[0][0] = 999
print(matrix) # [[999, 0, 0], [0, 0, 0], [0, 0, 0]]——正确
# 同理,字典的dict.fromkeys也有这个问题
# ❌ 所有键共享同一个列表值
d = dict.fromkeys(['a', 'b', 'c'], [])
d['a'].append(1)
print(d) # {'a': [1], 'b': [1], 'c': [1]}——所有值都变了
# ✅ 用字典推导式
d = {k: [] for k in ['a', 'b', 'c']}
d['a'].append(1)
print(d) # {'a': [1], 'b': [], 'c': []}——正确
10.2 陷阱二:可变默认参数
# ❌ 经典错误——可变默认参数
class TaskManager:
def __init__(self, tasks=[]):
self.tasks = tasks # 所有实例共享同一个列表!
def add(self, task):
self.tasks.append(task)
m1 = TaskManager()
m2 = TaskManager()
m1.add('task1')
m2.add('task2')
print(m1.tasks) # ['task1', 'task2']——m1的数据被污染了!
# ✅ 正确写法
class TaskManagerSafe:
def __init__(self, tasks=None):
self.tasks = list(tasks) if tasks is not None else []
def add(self, task):
self.tasks.append(task)
10.3 最佳实践总结
import copy
# 实践一:函数参数需要防御性拷贝
def process_data(data):
"""如果函数需要修改参数,且调用者不希望参数被修改,做深拷贝"""
data = copy.deepcopy(data)
data['processed'] = True
return data
# 实践二:从方法返回内部可变数据时,返回副本
class Cache:
def __init__(self):
self._data = {}
def get_all(self):
"""返回所有缓存数据的副本——防止外部修改内部状态"""
return copy.deepcopy(self._data)
def get(self, key):
"""返回单个值的副本——如果是可变类型"""
value = self._data.get(key)
if isinstance(value, (list, dict, set)):
return copy.deepcopy(value)
return value
# 实践三:工厂函数每次都创建新对象
def create_default_config():
"""每次调用都创建全新的配置——避免共享可变默认值"""
return {
'host': 'localhost',
'port': 8080,
'allowed_origins': [],
'database': {'host': 'db.local', 'pool': {'min': 1, 'max': 10}}
}
# 实践四:使用不可变类型避免拷贝的烦恼
# 如果数据不需要修改,直接用不可变类型
CONFIG = (
('host', 'localhost'),
('port', 8080),
)
# 任何人都无法意外修改
十一、性能考量
import copy
import time
small_list = [1, 2, 3, 4, 5]
large_list = list(range(10000))
nested_list = [list(range(100)) for _ in range(100)]
def benchmark(label, func, data, iterations=10000):
start = time.perf_counter()
for _ in range(iterations):
func(data)
elapsed = time.perf_counter() - start
print(f'{label}: {elapsed:.4f}秒 ({iterations}次)')
print('=== 小列表 (5个元素) ===')
benchmark('赋值', lambda x: x, small_list)
benchmark('浅拷贝(copy.copy)', copy.copy, small_list)
benchmark('浅拷贝(list.copy)', list.copy, small_list)
benchmark('浅拷贝([:])', lambda x: x[:], small_list)
benchmark('深拷贝', copy.deepcopy, small_list)
print('\n=== 大列表 (10000个元素) ===')
benchmark('赋值', lambda x: x, large_list)
benchmark('浅拷贝', copy.copy, large_list)
benchmark('深拷贝', copy.deepcopy, large_list)
print('\n=== 嵌套列表 (100个列表,每个100元素) ===')
benchmark('浅拷贝', copy.copy, nested_list)
benchmark('深拷贝', copy.deepcopy, nested_list)
# 💡 性能经验:
# 1. 赋值是最快的(几乎没有开销)
# 2. 浅拷贝开销中等(需要创建新容器和复制引用)
# 3. 深拷贝最慢(需要递归复制所有层级)
# 4. 对于简单数据(没有嵌套可变结构),浅拷贝通常就够用了
# 5. 深拷贝只在确实需要独立嵌套结构时才使用
十二、综合案例:安全缓存系统
import copy
import time
from threading import Lock
class SafeCache:
"""
一个线程安全、防数据污染的缓存系统。
设计原则:
1. 内部存储用可变类型(方便管理)
2. 所有对外暴露的数据都返回深拷贝(防止外部污染)
3. 所有外部写入的数据都做深拷贝(防止外部引用污染内部)
"""
def __init__(self, max_size=1000, default_ttl=3600):
self._max_size = max_size
self._default_ttl = default_ttl
self._store = {} # {key: (value, expire_time)}
self._lock = Lock()
self._hits = 0
self._misses = 0
def set(self, key, value, ttl=None):
"""设置缓存值。对value做深拷贝,防止外部后续修改影响缓存。"""
if ttl is None:
ttl = self._default_ttl
expire_time = time.time() + ttl if ttl > 0 else float('inf')
with self._lock:
stored_value = copy.deepcopy(value)
self._store[key] = (stored_value, expire_time)
if len(self._store) > self._max_size:
oldest_key = min(self._store.keys(), key=lambda k: self._store[k][1])
del self._store[oldest_key]
def get(self, key, default=None):
"""获取缓存值。返回深拷贝,防止外部修改影响缓存内部数据。"""
with self._lock:
if key in self._store:
value, expire_time = self._store[key]
if time.time() > expire_time:
del self._store[key]
self._misses += 1
return default
self._hits += 1
return copy.deepcopy(value)
else:
self._misses += 1
return default
@property
def stats(self):
"""获取缓存统计信息"""
with self._lock:
total = self._hits + self._misses
hit_rate = self._hits / total if total > 0 else 0
return {
'size': len(self._store),
'hits': self._hits,
'misses': self._misses,
'hit_rate': f'{hit_rate:.2%}',
}
# 使用示例
cache = SafeCache(max_size=5, default_ttl=60)
user_profile = {
'name': '小明',
'tags': ['Python', 'Web'],
'settings': {'theme': 'dark', 'notifications': True}
}
cache.set('user:1', user_profile)
# 外部修改原始数据——不影响缓存
user_profile['tags'].append('数据库')
user_profile['settings']['theme'] = 'light'
print('外部修改后原始数据:', user_profile)
print('缓存中的数据:', cache.get('user:1'))
# 缓存中的数据没变!
# 从缓存获取后修改——也不影响缓存
cached = cache.get('user:1')
cached['tags'].append('HACKED')
cached['name'] = '被篡改的名字'
print('\n获取后尝试污染...')
print('被修改的副本:', cached)
print('缓存中仍然是:', cache.get('user:1'))
# 缓存数据安全!
print('\n缓存统计:', cache.stats)
十三、本篇小结
Python中的三种数据"传递"方式及其核心区别:
赋值(=):
- 不给数据做任何拷贝,只是多贴一个标签
- 多个变量指向同一个对象,一改全改
- 对不可变类型通常"安全"(修改=创建新对象)
- 开销:几乎为零
浅拷贝(copy.copy):
- 创建新的容器对象,但容器内的元素还是共享原来的
- “复制了壳,没复制瓤”
- 修改顶层元素互不影响,修改嵌套元素相互影响
- 适用于:只需修改顶层结构、数据只有一层嵌套的场景
- 创建方式:
copy.copy()、list.copy()、dict.copy()、[:]、copy()方法等
深拷贝(copy.deepcopy):
- 递归复制所有层级的对象,完全独立
- 修改任意层级都不影响原始数据
- 自动处理循环引用
- 适用于:多环境配置、状态快照、Undo/Redo、数据隔离
- 可通过
__deepcopy__和__copy__自定义拷贝行为
选择建议:
- 只读取不修改 → 直接用赋值
- 只修改顶层元素 → 用浅拷贝
- 需要修改嵌套结构 → 用深拷贝
- 性能敏感场景 → 优先考虑能否用不可变类型避免拷贝
以上就是一文详解Python赋值传递中的深浅拷贝问题的详细内容,更多关于Python赋值传递深浅拷贝的资料请关注脚本之家其它相关文章!
