
本文深入解析 python 中字典等可变对象的赋值本质:`dict1 = dict2` 并非复制数据,而是共享同一内存对象;后续对字典内容的原地修改(如 `d[key] = val`)会影响所有引用者,而重新赋值(如 `d = {}`)则会切断引用关系。
在 Python 中,理解“赋值即绑定”(assignment is binding)是掌握对象行为的关键。变量名不是容器,而是指向对象的标签;而字典、列表等可变对象(mutable objects)一旦被多个变量引用,它们就共享同一块内存空间——这直接导致了你观察到的现象。
? 为什么第一个代码片段所有键都指向同一个字典?
set1 = {} # ✅ 创建一个空字典对象,set1 指向它
set2 = {}
for i in s1:
for j in range(1, len(i)):
set1[i[j][0]] = i[j][1] # ⚠️ 原地修改:向同一个字典插入/更新键值对
set2[i[0]] = set1 # ✅ 将 set1 的引用(而非副本)存入 set2这里 set1 始终指向同一个字典对象。每次循环中,你只是往这个同一个字典里不断添加或覆盖键值对;而 set2[i[0]] = set1 存储的始终是该字典的内存地址。最终 set2 的所有值都指向那个被反复修改的字典——因此所有键对应的值“看起来一样”,实则是同一对象的最终状态。
✅ 为什么第二个片段能正确工作?
set2 = {}
for i in s1:
set1 = {} # ✅ 关键!创建一个全新的字典对象,set1 现在指向新地址
for j in range(1, len(i)):
set1[i[j][0]] = i[j][1] # ✅ 向这个新字典写入数据
set2[i[0]] = set1 # ✅ 存储的是这个新字典的引用set1 = {} 是一次重新绑定操作:它让变量 set1 脱离旧字典,转而指向一个全新、独立的空字典。因此每次循环生成的 set1 都是不同的对象,set2 中每个键都保存了各自独立字典的引用,结构自然符合预期:{name: {value: pair}, name2: {...}}。
? 类比澄清:引用 vs 重绑定
你提到的 dictionary1 = dictionary2 后再 dictionary2 = {...} 不影响 dictionary1,这完全正确——但它揭示的正是同一原理:
立即学习“Python免费学习笔记(深入)”;
dictionary2 = {1: 'a', 2: 'b'}
dictionary1 = dictionary2 # ✅ dictionary1 和 dictionary2 指向同一字典对象
dictionary2 = {1: 'f', 2: 'g'} # ✅ 重新赋值:dictionary2 现在指向一个新字典
# dictionary1 仍指向原来的 {1:'a', 2:'b'} —— 引用未被破坏,只是变量绑定变了⚠️ 注意区别:
- d[key] = value → 原地修改(in-place mutation),影响所有引用者;
- d = {...} 或 d = [] → 重新绑定(rebinding),仅改变当前变量的指向,不影响其他变量。
? 实用建议与替代方案
- 默认安全做法:在循环内每次都用 {} 或 dict() 创建新字典,避免意外共享。
-
显式复制(如需复用旧结构):
set1 = original_dict.copy() # 浅拷贝(适用于键值均为不可变对象) # 或 import copy set1 = copy.deepcopy(original_dict) # 深拷贝(含嵌套可变对象时必需)
-
使用字典推导式更 Pythonic:
set2 = { i[0]: {pair[0]: pair[1] for pair in i[1:]} for i in s1 }
✅ 总结
Python 中没有“传值赋值”或“传引用赋值”的二分法——只有对象引用传递(pass by object reference)。可变对象的“共享性”源于多变量绑定到同一对象,而“隔离性”则依赖于显式创建新对象({})或深拷贝。理解 = 是绑定操作、d[k] = v 是方法调用(__setitem__),是写出可靠、可预测字典逻辑的基础。










