Python 字典的 keys()/values()/items() 返回什么?字典怎么合并?
简化版
**dict.keys()、dict.values()、dict.items() 返回的不是列表,而是「视图对象(view object)」——它们是字典的「动态窗口」,字典变化时视图会自动跟着变,而且几乎不占内存(不复制数据)。**关键点:① 视图是动态的——keys = d.keys() 之后往 d 里加键,keys 也会随之出现新键(它是活的引用,不是快照);② 视图支持集合运算——d1.keys() & d2.keys()(两字典的公共键)、|(并集)、-(差集),像集合一样操作(items 视图在值可哈希时也支持);③ 视图不能索引(d.keys()[0] 报错,要先 list(d.keys()))。字典合并的几种方式:① {**d1, **d2}(解包合并,3.5+,后者覆盖前者);② d1 | d2(合并运算符,3.9+,返回新字典)、d1 |= d2(原地更新);③ d1.update(d2)(原地更新 d1)。核心记忆:keys/values/items 返回动态视图(跟字典变、省内存、支持集合运算、不能索引),合并用 {**d1,**d2} 或 d1|d2(3.9+)或 update(原地)。
详细版
视图对象与合并:
| 操作 | 说明 |
|---|---|
d.keys() | 键视图(动态、支持集合运算) |
d.values() | 值视图(动态、不支持集合运算) |
d.items() | 键值对视图(动态、值可哈希时支持集合运算) |
{**d1, **d2} | 解包合并(3.5+,返回新字典) |
d1 | d2 | 合并运算符(3.9+,返回新字典) |
d1 |= d2 / d1.update(d2) | 原地更新 d1 |
d = {'a': 1, 'b': 2}
# 视图对象(不是列表)
ks = d.keys()
print(ks) # dict_keys(['a', 'b'])
print(type(ks)) # <class 'dict_keys'>
# 动态:字典变,视图跟着变
d['c'] = 3
print(ks) # dict_keys(['a', 'b', 'c']) ← 自动多了 c!
# 集合运算(键视图)
d1 = {'a': 1, 'b': 2}
d2 = {'b': 3, 'c': 4}
print(d1.keys() & d2.keys()) # {'b'}(公共键)
print(d1.keys() | d2.keys()) # {'a','b','c'}(所有键)
print(d1.keys() - d2.keys()) # {'a'}(d1 独有)
# 不能索引,要先转 list
# d.keys()[0] # ✗ TypeError
list(d.keys())[0] # ✓
# 字典合并
d1 = {'a': 1, 'b': 2}
d2 = {'b': 99, 'c': 3}
print({**d1, **d2}) # {'a':1, 'b':99, 'c':3}(d2 覆盖 d1 的 b)
print(d1 | d2) # 同上(3.9+)
d1 |= d2 # 原地更新 d1
d1.update(d2) # 原地更新(等价)
# 遍历 items 是最常用的
for k, v in d.items():
print(k, v)
⚠️ 两个核心认知:①
keys()/values()/items()是「视图」不是「列表」——它是字典的活窗口(字典改,视图跟着改)、几乎零内存开销(不复制),而且 keys 视图能当集合用做交并差;② 合并字典优先用d1 | d2(3.9+,清晰)或{**d1, **d2}(兼容老版本),冲突时「后面的覆盖前面的」。视图的「动态性」既是优点也是坑:优点是省内存、总反映最新状态;坑是「遍历字典时不能改字典大小」——for k in d.keys(): d[新键]=...或del d[k]会因为视图在遍历中变化而抛RuntimeError: dictionary changed size during iteration。要边遍历边改,先固化成列表for k in list(d.keys())。集合运算是视图的隐藏利器:求两个字典的公共键d1.keys() & d2.keys()、只在 d1 不在 d2 的键d1.keys() - d2.keys(),一行搞定,比手写循环优雅。
完整版教学
一、视图对象是什么
先理解视图对象的本质:
dict.keys()/values()/items() 返回"视图对象":
dict_keys、dict_values、dict_items
→ 不是列表!是字典的"动态视图"
三个视图:
d.keys() → 所有键的视图
d.values() → 所有值的视图
d.items() → 所有 (键,值) 元组的视图
视图的两大特性:
① 动态(live):反映字典的当前状态
字典变化 → 视图自动跟着变(不是快照)
② 轻量:几乎不占内存
不复制数据,只是"指向"字典的窗口
对比 Python 2:
Py2 的 keys()/values()/items() 返回"列表"(复制、快照)
Py3 改成"视图"(动态、省内存)
→ Py2 迁 Py3 时,需要列表的地方要 list(d.keys())
能做什么:
① 迭代:for k in d.keys()(或直接 for k in d)
② len:len(d.keys())
③ 成员判断:'a' in d.keys()(也可直接 'a' in d)
④ 集合运算:keys/items 支持 & | - ^
⑤ 转列表:list(d.keys())
不能做:
✗ 索引:d.keys()[0](视图不支持下标)
✗ 修改:视图是只读的(改要改字典本身)
所以视图对象(dict_keys 等)是字典的动态窗口:跟字典变+省内存,不是列表
dict.keys()/values()/items() 返回「视图对象」(dict_keys、dict_values、dict_items)——不是列表、是字典的「动态视图」。三个视图:keys(所有键)、values(所有值)、items(所有键值对元组)。视图的两大特性:① 动态(live)——反映字典当前状态、字典变化视图自动跟着变(不是快照);② 轻量——几乎不占内存、不复制数据、只是指向字典的窗口。对比 Python 2:Py2 返回列表(复制、快照)、Py3 改成视图(动态、省内存),Py2 迁 Py3 时需要列表的地方要 list(d.keys())。能做:迭代、len、成员判断、集合运算(keys/items)、转列表。不能做:索引(d.keys()[0] 不支持下标)、修改(只读)。理解「视图对象(dict_keys/values/items)是字典的动态窗口:①动态(跟字典变、不是快照)②轻量(不复制省内存);Py2 返回列表、Py3 改视图是迁移坑;能迭代/len/集合运算、不能索引/改」,就理解了视图对象。
二、视图的动态性
理解「动态」的表现与坑:
动态性表现:视图始终反映字典的当前状态
d = {'a': 1}
ks = d.keys() # ks 是视图
print(ks) # dict_keys(['a'])
d['b'] = 2 # 改字典
print(ks) # dict_keys(['a', 'b']) ← ks 自动更新!
del d['a']
print(ks) # dict_keys(['b'])
→ ks 不是拍了张照片,而是"活的引用"
好处:
① 省内存(不复制)
② 总是最新(不用重新取)
坑:遍历时改字典大小 → RuntimeError
for k in d.keys():
d[k*2] = 0 # ✗ 遍历中改变字典大小
→ RuntimeError: dictionary changed size during iteration
del 也一样:
for k in d.keys():
if cond: del d[k] # ✗ 同样报错
原因:视图动态,遍历中字典结构变了 → 迭代器失效
解决:先固化成列表(快照)
for k in list(d.keys()): # list() 复制一份,遍历它
if cond: del d[k] # ✓ 改的是 d,遍历的是快照
或:先收集要删的,遍历后统一删
to_del = [k for k in d if cond]
for k in to_del: del d[k]
改值(不改大小)是允许的:
for k in d:
d[k] = d[k] * 2 # ✓ 只改值,大小不变,OK
所以视图动态(跟字典变):好处省内存/最新,坑遍历中改大小报错(先 list()固化)
动态性表现:视图始终反映字典的当前状态——ks = d.keys() 后 d['b']=2、ks 自动更新(不是拍照片、是活的引用)。好处:① 省内存(不复制)、② 总是最新。坑:遍历时改字典大小 → RuntimeError——for k in d.keys(): d[k*2]=0 或 del d[k] 会抛 RuntimeError: dictionary changed size during iteration(视图动态、遍历中字典结构变了迭代器失效)。解决:先固化成列表(快照) for k in list(d.keys())(遍历快照、改的是 d)、或先收集要删的再统一删。改值(不改大小)是允许的:for k in d: d[k] = d[k]*2(只改值大小不变 OK)。理解「视图动态(跟字典变):好处省内存/最新;坑遍历中改字典大小抛 RuntimeError(迭代器失效);解决先 list()固化快照;改值不改大小允许」,就掌握了动态性。
三、视图的集合运算
理解 keys/items 视图的集合运算:
keys 视图和 items 视图支持"集合运算"(像 set):
(values 视图不支持,因为值可能重复/不可哈希)
集合运算符:
d1.keys() & d2.keys() # 交集:公共键
d1.keys() | d2.keys() # 并集:所有键
d1.keys() - d2.keys() # 差集:d1 有 d2 没有的键
d1.keys() ^ d2.keys() # 对称差:只在一个里的键
实用场景:
① 两字典的公共键:
common = d1.keys() & d2.keys()
② d1 独有的键(d2 里没有的):
only_d1 = d1.keys() - d2.keys()
③ 找出被改动的键(配合值比较):
changed = {k for k in d1.keys() & d2.keys() if d1[k] != d2[k]}
④ 合并所有键:
all_keys = d1.keys() | d2.keys()
items 视图的集合运算(值需可哈希):
d1.items() & d2.items() # 键和值都相同的项
→ 找两个字典"完全相同的键值对"
⚠️ 若某个值不可哈希(如 list),items 集合运算会报错
对比手写:
common = [k for k in d1 if k in d2] # 手写
common = d1.keys() & d2.keys() # 视图(更简洁)
返回的是 set:
d1.keys() & d2.keys() → 普通 set(不是视图)
所以 keys/items 视图支持集合运算(&|-^),一行求公共键/独有键/相同项
keys 视图和 items 视图支持「集合运算」(像 set)(values 视图不支持、因值可能重复/不可哈希)。集合运算符:d1.keys() & d2.keys()(交集:公共键)、|(并集:所有键)、-(差集:d1 有 d2 没有的键)、^(对称差:只在一个里的键)。实用场景:① 两字典的公共键、② d1 独有的键、③ 找被改动的键({k for k in d1.keys() & d2.keys() if d1[k] != d2[k]})、④ 合并所有键。items 视图的集合运算(值需可哈希):d1.items() & d2.items() 找完全相同的键值对(若某个值不可哈希如 list 会报错)。对比手写:d1.keys() & d2.keys() 比 [k for k in d1 if k in d2] 更简洁。返回的是 set(不是视图)。理解「keys/items 视图支持集合运算(&交/|并/-差/^对称差)、values 不支持;一行求公共键 d1.keys()&d2.keys()/独有键-/相同项 items()&;items 集合运算要值可哈希」,就掌握了集合运算。
四、字典合并的几种方式
理解字典合并的各种方法:
字典合并方式(冲突时"后者覆盖前者"):
① {**d1, **d2}(解包合并,Python 3.5+):
merged = {**d1, **d2}
→ 返回新字典,d2 的键覆盖 d1 的同名键
→ 可合并多个:{**d1, **d2, **d3}
→ 可加临时键:{**d1, 'extra': 1}
② d1 | d2(合并运算符,Python 3.9+):
merged = d1 | d2
→ 返回新字典,语义同解包,更简洁
→ d1 |= d2:原地更新 d1(等价 d1.update(d2))
③ d1.update(d2)(原地更新,最老):
d1.update(d2) # 改的是 d1(无返回值)
→ 把 d2 的键值加入 d1,同名覆盖
→ 也可 update(可迭代的键值对)、update(k=v)
对比:
{**d1,**d2}、d1|d2 → 返回新字典(d1、d2 不变)
d1|=d2、d1.update(d2) → 原地改 d1
覆盖规则(都一样):
同名键,后面的值覆盖前面的
{**{'a':1}, **{'a':2}} → {'a':2}
d1={'a':1}; d1|{'a':2} → {'a':2}
嵌套字典的坑(浅合并):
这些都是"浅合并"——只合并顶层键
{**{'x':{'a':1}}, **{'x':{'b':2}}}
→ {'x':{'b':2}} # x 整个被覆盖,不是深度合并!
→ 深度合并要递归或用第三方库(如 mergedeep)
选择:
要新字典、清晰 → d1 | d2(3.9+)或 {**d1,**d2}
原地更新 → d1.update(d2) 或 d1 |= d2
所以合并:{**d1,**d2}/d1|d2(新字典)、update/|=(原地),后者覆盖前者,浅合并
字典合并方式(冲突时「后者覆盖前者」):① {**d1, **d2}(解包合并,3.5+)——返回新字典、d2 覆盖 d1 同名键、可合并多个、可加临时键;② d1 | d2(合并运算符,3.9+)——返回新字典、语义同解包更简洁、d1 |= d2 原地更新;③ d1.update(d2)(原地更新,最老)——改 d1、同名覆盖。对比:{**d1,**d2}/d1|d2 返回新字典、d1|=d2/update 原地改 d1。覆盖规则:同名键后面的值覆盖前面的。嵌套字典的坑(浅合并):这些都是浅合并、只合并顶层键({**{'x':{'a':1}}, **{'x':{'b':2}}} → {'x':{'b':2}} x 整个被覆盖不是深度合并)、深度合并要递归或第三方库。选择:要新字典用 d1 | d2(3.9+)或 {**d1,**d2}、原地更新用 update 或 |=。理解「合并:{**d1,**d2}/d1|d2 返回新字典、update/|=原地改;后者覆盖前者;都是浅合并(嵌套字典顶层键整个覆盖不是深度合并)」,就掌握了字典合并。
五、遍历字典的正确姿势
理解字典遍历的常见写法:
遍历字典的几种方式:
① 遍历键(默认):
for k in d: # 直接遍历字典 = 遍历键
for k in d.keys(): # 等价(更显式)
→ 两者一样,for k in d 更常用
② 遍历值:
for v in d.values():
③ 遍历键值对(最常用):
for k, v in d.items(): # 同时拿键和值
→ items() 自动解包成 k, v
④ 带索引遍历:
for i, (k, v) in enumerate(d.items()):
⑤ 排序遍历:
for k in sorted(d): # 按键排序
for k, v in sorted(d.items()): # 按键
for k, v in sorted(d.items(), key=lambda x: x[1]): # 按值
性能提示:
for k in d: v = d[k] # ✗ 每次索引查找一次
for k, v in d.items(): # ✓ 一次拿到,更快
→ 要键和值就用 items(),别 for k in d 再 d[k]
遍历中修改的规则(再强调):
改值(不变大小):for k in d: d[k] = ... ✓
改大小(增删键):for k in list(d): ... ✓(先固化)
for k in d: del d[k] ✗ RuntimeError
删除并遍历的惯用法:
d = {k: v for k, v in d.items() if keep(k, v)} # 推导式重建(干净)
所以遍历:for k in d(键)/d.values()/d.items()(键值);要键值用 items 更快;改大小先 list
遍历字典的几种方式:① 遍历键(默认) for k in d(= 遍历键,等价 for k in d.keys());② 遍历值 for v in d.values();③ 遍历键值对(最常用) for k, v in d.items()(自动解包);④ 带索引 for i, (k, v) in enumerate(d.items());⑤ 排序遍历 for k in sorted(d)(按键)、sorted(d.items(), key=lambda x: x[1])(按值)。性能提示:for k in d: v = d[k](每次索引查找)不如 for k, v in d.items()(一次拿到更快)。遍历中修改的规则:改值(不变大小)OK、改大小先固化 for k in list(d)、for k in d: del d[k] 报错。删除并遍历的惯用法:d = {k: v for k, v in d.items() if keep(k, v)}(推导式重建)。理解「遍历:for k in d(键)/values()/items()(键值最常用);要键值用 items 更快别 for k in d 再 d[k];改大小先 list、删除用推导式重建」,就掌握了遍历姿势。
六、总结与实践
总结视图和合并:
视图对象速查:
d.keys()/values()/items() → 动态视图(不是列表)
特性:动态(跟字典变)、轻量(不复制)
能:迭代、len、in、集合运算(keys/items)、转 list
不能:索引、修改
坑:遍历中改字典大小 → RuntimeError(先 list 固化)
集合运算(keys/items):
&(公共)| (全部)-(差集)^(对称差)
d1.keys() & d2.keys() # 一行求公共键
字典合并:
{**d1, **d2} # 新字典(3.5+)
d1 | d2 # 新字典(3.9+,推荐)
d1 |= d2 / d1.update(d2) # 原地
规则:后者覆盖前者、浅合并(嵌套字典顶层覆盖)
遍历:
for k in d # 键
for k, v in d.items() # 键值(最常用、最快)
核心总结:
keys/values/items 返回动态视图(省内存、跟字典变、可集合运算)
合并用 {**d1,**d2} 或 d1|d2(3.9+),后者覆盖前者
遍历改大小要先 list()(避免 RuntimeError)
所以视图动态省内存+集合运算,合并用{**}或|(后覆盖前),遍历改大小先 list
视图对象速查:d.keys()/values()/items() → 动态视图(不是列表),特性动态(跟字典变)+轻量(不复制),能迭代/len/in/集合运算/转 list、不能索引/修改,坑是遍历中改字典大小 RuntimeError(先 list 固化)。集合运算(keys/items):&(公共)|(全部)-(差集)^(对称差)。字典合并:{**d1,**d2}(3.5+)、d1|d2(3.9+推荐)、d1|=d2/update(原地),后者覆盖前者、浅合并。遍历:for k in d(键)、for k, v in d.items()(键值最常用最快)。理解「视图动态省内存+可集合运算、不能索引、遍历改大小先 list;合并{**}或|(3.9+)后覆盖前浅合并;遍历用 items 最常用」,就掌握了总结与实践。
记忆钩子:**「dict.keys()/values()/items()返回的不是列表而是『视图对象』(dict_keys/dict_values/dict_items)——①动态(live):字典变化视图自动跟着变(是活的引用不是快照,ks=d.keys()后 d[‘c’]=3 则 ks 里自动出现 c)②轻量:几乎不占内存(不复制数据、只是指向字典的窗口);★Py2 返回列表、Py3 改成视图是迁移坑(需要列表就 list(d.keys()));能做:迭代/len/in/集合运算/转 list,不能索引(d.keys()[0]报错)也不能改;★keys 和 items 视图支持集合运算(&交集=公共键、|并集、-差集=独有键、^对称差),values 不支持;d1.keys()&d2.keys()一行求两字典公共键;★动态性的坑:遍历时增删键(改字典大小)会抛 RuntimeError:dictionary changed size during iteration(迭代器失效),要先固化 for k in list(d.keys())(改值不改大小则允许);字典合并:{**d1,d2}(3.5+)或 d1|d2(3.9+)返回新字典、d1|=d2 或 d1.update(d2)原地更新,冲突时后者覆盖前者,都是浅合并(嵌套字典顶层键整个被覆盖不是深度合并);遍历要键值用 for k,v in d.items()(比 for k in d 再 d[k]快)」。
七、常见误区与追问
- 误区:d.keys() 返回一个键的列表。 返回的是「视图对象」(dict_keys)不是列表——它是字典的动态窗口(字典变化时视图自动跟着变)、几乎不占内存(不复制数据);这是 Python 3 的行为(Python 2 才返回列表快照);需要真正的列表(比如要索引
[0]、要排序后固定)时要显式list(d.keys())。 - 误区:把 keys() 存下来后字典再改,keys() 还是旧的。 不是——视图是「动态/活的」引用而不是快照:
ks = d.keys()之后往 d 增删键,ks会自动反映这些变化(d['c']=3后ks里就有了 c);如果你需要「当时的快照」,要ks = list(d.keys())复制一份;这个动态性省内存但也带来「遍历时不能改字典大小」的限制。 - 误区:遍历字典时可以随意增删键。 不行——因为视图是动态的,遍历过程中增删键(改变字典大小)会导致迭代器失效、抛
RuntimeError: dictionary changed size during iteration;要边遍历边删,先把键固化成列表for k in list(d.keys()): ...或用字典推导式重建d = {k:v for k,v in d.items() if keep(k)};注意「只改值不改大小」(for k in d: d[k] = ...)是允许的。 - **误区:{**d1, d2} 和 d1 | d2 是深度合并嵌套字典。 都是浅合并——只合并顶层的键,如果某个键的值本身是字典,冲突时是整个替换而不是递归合并:
{**{'x':{'a':1}}, **{'x':{'b':2}}}得到{'x':{'b':2}}(x 的值被整个覆盖成{'b':2},不是{'a':1,'b':2});需要深度合并(递归合并嵌套字典)要自己写递归函数或用第三方库(如 mergedeep)。 - 追问:dict 的视图对象有哪些独特能力,为什么设计成视图而不是列表? 独特能力:① 动态——总是反映字典的当前状态,不用重新取;② 轻量——不复制数据、几乎零内存开销;③ keys 和 items 视图支持集合运算(
&交集求公共键、|并集、-差集求独有键、^对称差),可以一行完成两个字典键的比较;设计成视图(而非 Python 2 的列表)主要为了省内存和保持最新——大字典调用 keys() 不必复制一份键列表出来,且视图随字典自动更新;代价是不能像列表那样索引(要先list())、遍历时不能改字典大小。 - 追问:合并两个字典有哪几种方式,冲突时谁覆盖谁? 主要三种:①
{**d1, **d2}(字典解包,3.5+,返回新字典、d1、d2 不变,可合并多个{**d1,**d2,**d3}、可加临时键);②d1 | d2(合并运算符,3.9+,返回新字典、语义同解包但更清晰)以及d1 |= d2(原地更新 d1);③d1.update(d2)(原地更新 d1、无返回值、最兼容老版本);冲突规则都一样——「后面的覆盖前面的」,即同名键取靠后那个字典的值({**{'a':1}, **{'a':2}}得{'a':2});注意都是浅合并;选择:要新字典优先d1 | d2(3.9+)或{**d1,**d2},要原地改用update或|=。 - 追问:怎么用视图的集合运算找出两个字典的公共键、各自独有的键、以及值发生变化的键? 公共键(都有的键):
d1.keys() & d2.keys();d1 独有(在 d1 不在 d2):d1.keys() - d2.keys();d2 独有:d2.keys() - d1.keys();只在其中一个里的键(对称差):d1.keys() ^ d2.keys();所有键:d1.keys() | d2.keys();值发生变化的键(在两个字典都有但值不同):结合公共键和值比较{k for k in d1.keys() & d2.keys() if d1[k] != d2[k]};这些都比手写循环判断简洁,返回的是普通 set;注意 values 视图不支持集合运算(值可能重复或不可哈希),items 视图支持但要求所有值可哈希。
八、加强记忆
dict.keys()/values()/items() 返回的不是列表而是「视图对象」(dict_keys/dict_values/dict_items):① 动态(live)——字典变化时视图自动跟着变(是活的引用不是快照,ks = d.keys() 后 d['c']=3 则 ks 里自动出现 c);② 轻量——几乎不占内存(不复制数据、只是指向字典的窗口)。Py2 返回列表、Py3 改成视图是迁移坑(需要列表就 list(d.keys()))。能迭代/len/in/集合运算/转 list,不能索引(d.keys()[0] 报错)也不能改。keys 和 items 视图支持集合运算(& 交集=公共键、| 并集、- 差集=独有键、^ 对称差),values 不支持——d1.keys() & d2.keys() 一行求公共键。动态性的坑:遍历时增删键(改字典大小)会抛 RuntimeError: dictionary changed size during iteration,要先固化 for k in list(d.keys())(改值不改大小则允许)。字典合并:{**d1, **d2}(3.5+)或 d1 | d2(3.9+)返回新字典、d1 |= d2 或 d1.update(d2) 原地更新,冲突时后者覆盖前者、都是浅合并(嵌套字典顶层键整个被覆盖)。一句话「**keys/values/items 返回动态视图(跟字典变、省内存、不是列表);keys/items 支持集合运算(&公共键/-独有键);遍历中改字典大小抛 RuntimeError(先 list 固化);合并用{**d1,d2}或 d1|d2(3.9+)后者覆盖前者浅合并、update/|=原地」。