三、字典与集合(P23–P32)
P23 · 知识点:字典创建
【题目】在变量 a 未定义的前提下,下列哪个表达式能正确创建字典?
A. (a: 1) B. {a: 1} C. dict("a", "1") D. dict(a=1)
答案:D
【考点】字典的多种创建方式。
【结论】选 D。dict(a=1) 使用关键字参数形式创建字典,键自动转为字符串 "a",得到 {"a": 1}。
【逐项辨析】 D. dict(a=1):正确。关键字参数形式,等价于 {"a": 1}。 B. {a: 1}:错误。若变量 a 未定义,会抛出 NameError;只有当 a 已被赋值时才以变量值为键。 C. dict("a", "1"):错误。dict() 不接受两个独立的位置参数,正确形式应为 dict([("a", 1)]) 或 dict(a=1)。 A. (a: 1):错误。圆括号内使用冒号是语法错误,Python 不支持此语法。
【知识点】 Python 创建字典的常见方式有以下四种:
- 字面值:{"a": 1, "b": 2},最直观常用。
- 关键字参数:dict(a=1, b=2),键必须是合法标识符,自动转为字符串。
- 可迭代对象:dict([("a", 1), ("b", 2)]),接收包含键值对元组的序列。
- fromkeys:dict.fromkeys(["a", "b"], 0),批量初始化同值键,默认值为 None。
注意,关键字参数中的键会被强制转为字符串,且不能是 Python 关键字或非法标识符。若键名包含空格、以数字开头或为保留字,则必须使用字面值或可迭代对象形式。
【记忆锚点】 “dict() 有三招:花括号直接写、关键字传参、序列套元组。”
【易混对比】
| 创建方式 | 示例 | 适用场景 |
|---|---|---|
| 字面值 {} | 静态定义,最常用 | |
| 关键字参数 | dict(a=1) | 键为合法标识符时 |
| 可迭代对象 | dict([("a", 1)]) | 动态构建键值对 |
| fromkeys | dict.fromkeys("ab", 0) | 批量初始化同值 |
换问法:若 a = "x",则 {a: 1} 的结果是什么?答:{"x": 1},此时 a 是已定义变量,以变量值为键。
【自测】 以下代码输出什么?
print(dict(a=1, b=2))
print(dict([("c", 3)]))答:{'a': 1, 'b': 2} 和 {'c': 3}。与第 P24 题连考。
【知识关联】
- 同库关联:与 P24–P28 字典题群;与 J34(Java HashMap)对照。
- 实现层:CPython dict 基于哈希表,紧凑有序(Py3.7+ 保证插入序)。
- 面试追问:① 字典有序吗?②
{}与dict()?
【拓展延伸】
- 变式问法:字典推导;
dict.fromkeys。 - 版本差异:Py3.6 实现有序,3.7 语言保证有序。
- 工程注意点:JSON 映射首选 dict;需要排序遍历用 sorted(d)。
P24 · 知识点:字典键要求
【题目】下列哪个类型可以作为字典的键?
A. 列表 B. 字典 C. 元组 D. 集合
答案:C
【考点】字典键必须是可哈希(不可变)类型。
【结论】选 C。字典的键必须是可哈希对象,元组在元素均可哈希时可作为键。
【逐项辨析】 A. 列表:错误。列表是可变类型,不可哈希,不能作为字典键。 B. 字典:错误。字典是可变类型,不可哈希,不能作为字典键。 C. 元组:正确。元组是不可变类型,只要其所有元素均可哈希,即可作为字典键。 D. 集合:错误。集合是可变类型,不可哈希,不能作为字典键。
【知识点】 可哈希(hashable)对象需满足三个条件:
- 生命周期内哈希值不变;
- 可与其他对象比较相等;
- 相等的对象哈希值必须相同。
Python 内置不可变类型如 int、float、str、tuple、frozenset、bytes 等均可哈希。可变类型如 list、dict、set 因内容可变导致哈希值不稳定,故不可哈希。
可用 hash(obj) 函数测试对象是否可哈希,不可哈希对象调用 hash() 会抛出 TypeError。
元组作为键有特殊限制:若元组包含列表等不可哈希元素,则整个元组不可哈希。例如 hash((1, [2])) 会抛出 TypeError。
【记忆锚点】 “键要哈希找不变,列表字典集合免;元组元素全得稳,嵌套可变也完蛋。”
【易混对比】
| 类型 | 可变性 | 可哈希 | 能否作字典键 |
|---|---|---|---|
| int | 不可变 | 是 | 能 |
| str | 不可变 | 是 | 能 |
| tuple | 不可变 | 元素可哈希则可 | 能(有条件) |
| list | 可变 | 否 | 不能 |
| dict | 可变 | 否 | 不能 |
| set | 可变 | 否 | 不能 |
| frozenset | 不可变 | 是 | 能 |
换问法:t = (1, [2]),能否用 t 作为字典的键?答:不能,因为元组中包含不可哈希的列表,整体不可哈希。
【自测】
d = {}
t = (1, "a")
d[t] = 10
print(len(d))答:1。与第 P25 题连考。
【知识关联】
- 同库关联:与 P23、P07(可哈希)、J36(Java null key)。
- 实现层:key 必须可哈希且比较相等语义一致;
__hash__与__eq__契约。 - 面试追问:① 列表能否作 key?② 元组何时可以?
【拓展延伸】
- 变式问法:
{(1,2):"a"}合法;{[1]:"a"}TypeError。 - 版本差异:语义稳定。
- 工程注意点:自定义对象作 key 要冻结字段;frozenset 可作 key。
P25 · 知识点:get 与下标取值
【题目】d = {"a": 1},d.get("b") 和 d["b"] 的结果分别是?
A. None 和 None B. 都抛 KeyError C. 都是 None D. None 和抛 KeyError
答案:D
【考点】字典取值的安全方式:get 带默认值,下标不存在抛 KeyError。
【结论】选 D。d.get("b") 返回 None,d["b"] 抛出 KeyError。
【推导过程】
d = {"a": 1}
d.get("b") # 键 "b" 不存在,返回默认值 None
d["b"] # 键 "b" 不存在,抛出 KeyError【逐项辨析】 D. None 和抛 KeyError:正确。get() 找不到键时返回默认值 None;下标访问找不到键时抛出 KeyError。 B. 都抛 KeyError:错误。get() 方法在键不存在时不会抛异常。 C. 都是 None:错误。下标访问 d["b"] 会抛出 KeyError,不会返回 None。 A. None 和 None:错误。下标访问会抛异常,不会返回 None。
【知识点】 dict.get(key, default=None) 是安全取值的首选方式:键存在时返回对应值;键不存在时返回 default(默认为 None);不会修改原字典。
d[key] 是直接访问:键存在时返回对应值;键不存在时抛出 KeyError,程序可能因此中断。
在需要区分“键不存在”和“值为 None”的场景,可使用 key in d 先判断成员关系,或使用 collections.defaultdict 实现自动填充默认值。
【记忆锚点】 “中括号硬取会报错,get 方法温柔有默认值。”
【易混对比】
| 方式 | 键不存在时 | 是否抛异常 | 可设默认值 | 是否修改字典 |
|---|---|---|---|---|
| d[k] | 抛 KeyError | 是 | 否 | 否 |
| d.get(k) | 返回 None | 否 | 是(第二参数) | 否 |
| d.setdefault(k, v) | 插入 k:v 并返回 v | 否 | 是 | 是 |
换问法:d.get("b", 0) 的结果是什么?答:0,因为指定了默认值参数。
【自测】
d = {"a": None}
print(d.get("a", "default"))
print(d.get("b", "default"))答:None 和 "default"。注意 get 的默认值只在键不存在时生效,键存在但值为 None 仍返回 None。与第 P26 题连考。
【知识关联】
- 同库关联:与 P28(删除)、P23;get 默认值 vs KeyError。
- 实现层:
d[k]缺失抛 KeyError;get(k,default)返回默认。 - 面试追问:① 如何安全取值?② setdefault 用途?
【拓展延伸】
- 变式问法:
d.get("x", [])别名坑;collections.defaultdict。 - 版本差异:Py3.9
d | other合并。 - 工程注意点:计数用 defaultdict/Counter;嵌套取值可用结构化解析。
P26 · 知识点:字典遍历
【题目】for k in d: 遍历的是字典的?
A. 索引 B. 值 C. 键值对 D. 键
答案:D
【考点】字典遍历方式:默认键、values() 值、items() 键值对。
【结论】选 D。for k in d 默认遍历字典的键,等价于 for k in d.keys()。
【逐项辨析】 D. 键:正确。for k in d 等价于 for k in d.keys(),遍历所有键。 B. 值:错误。遍历值需显式使用 d.values()。 C. 键值对:错误。遍历键值对需使用 d.items(),每次迭代得到 (key, value) 元组。 A. 索引:错误。字典是映射类型,不存在 list 那样的整数索引概念。
【知识点】 Python 字典提供三种标准遍历方式:
- for k in d:遍历键,可简写,返回 dict_keys 视图对象。
- for v in d.values():遍历值,返回 dict_values 视图对象。
- for k, v in d.items():遍历键值对,返回 dict_items 视图对象,支持元组解包。
Python 3.7+ 中字典保持插入顺序,但无整数索引。若需要按序号处理,应使用 enumerate(d) 获取序号与键的配对,或先转为 list。
视图对象(view object)与列表的区别:视图是原字典的动态映射,原字典修改后视图会反映变化,但视图本身不支持索引。
【记忆锚点】 “直接遍历是键,values 是值,items 成对出。”
【易混对比】
| 遍历方式 | 写法 | 每次迭代得到 | 返回类型 |
|---|---|---|---|
| 键 | for k in d | 键 | dict_keys |
| 值 | for v in d.values() | 值 | dict_values |
| 键值对 | for k, v in d.items() | (键, 值) 元组 | dict_items |
换问法:for i, k in enumerate(d): 中 i 和 k 分别代表什么?答:i 是枚举序号(从 0 开始),k 是字典的键。
【自测】
d = {"x": 1, "y": 2}
print(list(d))
print(list(d.values()))答:['x', 'y'] 和 [1, 2]。与第 P27 题连考。
【知识关联】
- 同库关联:与 P23 有序性;items/keys/values 视图。
- 实现层:Py3 返回动态视图,修改字典时迭代会 RuntimeError。
- 面试追问:① 遍历中删除怎么办?② items 与 list(items)?
【拓展延伸】
- 变式问法:
for k in d与for k in d.keys();按值排序遍历。 - 版本差异:Py3 视图 vs Py2 列表拷贝。
- 工程注意点:边遍历边改先
list(d.items())。
P27 · 知识点:字典更新
【题目】d = {"a": 1}; d.update({"a": 2, "b": 3}); 执行后 d 是?
A. 抛异常 B. {'a': 1, 'b': 3} C. {'a': 2} D.
答案:D
【考点】update() 合并字典:已有键覆盖,新键添加。
【结论】选 D。update() 原地更新字典,已有键 "a" 被覆盖为 2,新键 "b" 被添加为 3。
【推导过程】
d = {"a": 1}
d.update({"a": 2, "b": 3})
# 键 "a" 已存在,值由 1 覆盖为 2
# 键 "b" 不存在,新增 "b": 3
# d 变为 {"a": 2, "b": 3}【逐项辨析】 D. {'a': 2, 'b': 3}:正确。"a" 被覆盖为 2,"b" 被新增为 3。 B. {'a': 1, 'b': 3}:错误。update 会覆盖已有键的值,"a" 不会保持原值。 C. {'a': 2}:错误。update 不会删除未涉及的键,且会添加新键 "b"。 A. 抛异常:错误。update 传入字典是合法操作,不会抛异常。
【知识点】 dict.update([other]) 的行为:接收字典或键值对可迭代对象;对已有键用新值覆盖旧值;对不存在键添加新键值对;操作是原地修改,返回值为 None。
现代 Python 提供更多合并方式:
- Python 3.9+ 支持合并运算符:d | other 返回新字典,d |= other 原地更新。
- Python 3.5+ 支持解包合并:{**d1, **d2} 返回新字典。
注意区分返回新对象与原地修改:update() 和 |= 是原地操作,| 和 {**d1, **d2} 创建新对象。
【记忆锚点】 “update 更新有两条:旧键覆盖新键招,返回 None 别搞错。”
【易混对比】
| 方式 | 是否原地修改 | 返回值 | 适用版本 |
|---|---|---|---|
| d.update(d2) | 是 | None | 通用 |
| d | d2 | 否 | 新字典 | Python 3.9+ |
| d |= d2 | 是 | None | Python 3.9+ |
| 否 | 新字典 | Python 3.5+ |
换问法:d = {"a": 1}; d.update(a=2); print(d) 输出什么?答:{'a': 2},关键字参数形式同样会覆盖已有键。
【自测】
d1 = {"a": 1}
d2 = d1
d1.update({"b": 2})
print(d2 is d1)答:True。update 是原地修改,d2 仍引用同一对象,is 判断为真。与第 P28 题连考。
【知识关联】
- 同库关联:与 P25/P28;update 合并。
- 实现层:
update就地合并;|合并出新字典(3.9+)。 - 面试追问:① 同 key 谁覆盖谁?② 与 setdefault?
【拓展延伸】
- 变式问法:
d.update(e=1);{**a,**b}。 - 版本差异:3.9 合并运算符。
- 工程注意点:配置合并注意优先级;浅合并嵌套要递归。
P28 · 知识点:字典删除
【题目】d = {"a": 1, "b": 2}; d.pop("a") 的返回值是?执行后 d 是?
A. None 和 {'b': 2} B. 抛异常 C. 1 和 {'a': 1, 'b': 2} D. 1 和
答案:D
【考点】pop(key) 删除并返回值;del 删除不返回;popitem() 删除末尾项。
【结论】选 D。pop("a") 删除键 "a" 并返回其值 1,执行后 d 仅保留 {'b': 2}。
【推导过程】
d = {"a": 1, "b": 2}
d.pop("a") # 删除键 "a",返回对应的值 1
# d 变为 {"b": 2}【逐项辨析】 D. 1 和 {'b': 2}:正确。pop("a") 返回被删除的值 1,原字典删除该键值对。 B. 抛异常:错误。键 "a" 存在于字典中,pop 不会抛异常。 C. 1 和 {'a': 1, 'b': 2}:错误。pop 会真正删除键值对,不会保留 "a"。 A. None 和 {'b': 2}:错误。pop 返回的是被删除键对应的值 1,不是 None。
【知识点】 字典删除的三种主要方式:
- pop(key[, default]):删除指定键并返回其值;键不存在时返回 default,未提供 default 则抛 KeyError。
- del d[key]:删除指定键值对,不返回值;键不存在抛 KeyError。
- popitem():删除并返回最后插入的键值对(LIFO 顺序,Python 3.7+),字典为空时抛 KeyError。
clear() 可清空整个字典,使其变为空字典 {}。
注意 Python 3.7+ 中字典保持插入顺序,popitem() 按后进先出(LIFO)移除。早期版本(3.6 及以前)中 popitem() 移除顺序是任意的。
【记忆锚点】 “pop 有返回值,del 只管删,popitem 弹末尾,clear 全清空。”
【易混对比】
| 方法 | 是否返回值 | 删除对象 | 键不存在时 |
|---|---|---|---|
| pop(key) | 是(值) | 指定键 | 抛 KeyError |
| pop(key, default) | 是(值或 default) | 指定键 | 返回 default |
| del d[key] | 否 | 指定键 | 抛 KeyError |
| popitem() | 是(键值对元组) | 最后插入项 | 抛 KeyError |
| clear() | None | 全部元素 | 无 |
换问法:d.pop("c", 0) 的结果是什么?答:0,因为键 "c" 不存在且提供了默认值参数。
【自测】
d = {"a": 1, "b": 2}
print(d.popitem())
print(d)答:('b', 2) 和 {'a': 1}(Python 3.7+ 按 LIFO 顺序弹出最后插入项)。与第 P29 题连考。
【知识关联】
- 同库关联:与 P25;pop/del/popitem。
- 实现层:pop 可给默认;del 缺失 KeyError;popitem 弹出(3.7+ 后进先出)。
- 面试追问:① 清空字典?② pop 与 del 选择?
【拓展延伸】
- 变式问法:
d.pop(k, None);d.clear()。 - 版本差异:popitem 语义 Py3.7 明确 LIFO。
- 工程注意点:清理缓存条目用 pop;不要遍历中 del 原字典。
P29 · 知识点:集合特性
【题目】关于集合(set)的说法,正确的是?
A. 集合是有序的 B. 集合允许重复元素 C. 集合中的元素必须可哈希(不可变) D. 集合支持下标索引访问
答案:C
【考点】集合三大特性:无序、唯一、元素可哈希。
【结论】选 C。集合中的元素必须是不可变的可哈希对象。
【逐项辨析】 A. 集合是有序的:错误。集合是无序容器,不保证元素顺序,也不支持索引访问。 B. 集合允许重复元素:错误。集合具有唯一性,重复元素会被自动去重。 C. 集合中的元素必须可哈希(不可变):正确。与字典键的要求一致,元素必须是不可变类型。 D. 集合支持下标索引访问:错误。集合无序,不支持索引、切片等序列操作。
【知识点】 集合(set)的三大核心特性:
- 无序性:元素没有固定顺序,不支持索引访问和切片操作。
- 唯一性:自动去重,重复元素只保留一个。
- 元素可哈希:所有元素必须是不可变类型(如数字、字符串、元组),列表和字典不能作为集合元素。
集合底层基于哈希表实现,因此成员检测(x in s)的时间复杂度为 O(1),效率远高于列表的 O(n)。frozenset 是不可变集合,本身可哈希,可作为字典键或其他集合的元素。
【记忆锚点】 “集合三特性:无序、唯一、要哈希。”
【易混对比】
| 特性 | 列表 list | 集合 set | 元组 tuple |
|---|---|---|---|
| 有序 | 是 | 否 | 是 |
| 可重复 | 是 | 否 | 是 |
| 可变 | 是 | 是 | 否 |
| 可哈希 | 否 | 否 | 是(仅当元素都可哈希时;含列表即不可哈希,hash((1, [2])) 实测抛 TypeError: unhashable type: 'list',与 P24、P11 的口径一致) |
| 支持索引 | 是 | 否 | 是 |
| 成员检测复杂度 | O(n) | O(1) | O(n) |
换问法:{1, 1, 2, 2, 3} 的结果是什么?答:{1, 2, 3},重复元素被自动去重。
【自测】
s = {1, 2, 3}
s.add(3)
print(len(s))答:3。添加重复元素 3 不会改变集合大小,集合自动去重。与第 P30 题连考。
【知识关联】
- 同库关联:与 P30/P31/P32、J37(HashSet)。
- 实现层:set 基于哈希,无序(实现可能碰巧有序但不可依赖)、自动去重。
- 面试追问:① set 与 frozenset?② 如何保留插入序?
【拓展延伸】
- 变式问法:
set([1,1,2]);空 set 必须set()不能{}。 - 版本差异:语义稳定。
- 工程注意点:去重+成员判断用 set;需要序用 dict.fromkeys 或 sorted。
P30 · 知识点:集合运算
【题目】 & {2, 3, 4} 的结果是?
A. {2, 3, 4} B. {1, 2, 3, 4} C. {1, 4} D.
答案:D
【考点】集合运算符号:& 交集、| 并集、- 差集、^ 对称差。
【结论】选 D。& 运算符表示交集,返回两集合共有的元素 {2, 3}。
【推导过程】
{1, 2, 3} & {2, 3, 4}
# & 为交集运算符,保留两集合共有元素
# 共有元素:2, 3
# 结果:{2, 3}【逐项辨析】 D. {2, 3}:正确。两集合共有的元素是 2 和 3。 B. {1, 2, 3, 4}:错误。这是并集运算符 | 的结果。 C. {1, 4}:错误。这是对称差集运算符 ^ 的结果。 A. {2, 3, 4}:错误。无对应标准集合运算会产生此结果。
【知识点】 Python 集合的四种基本运算:
- 交集 & / intersection():两集合共有元素。
- 并集 | / union():两集合所有元素(自动去重)。
- 差集 - / difference():在左集合但不在右集合的元素。
- 对称差集 ^ / symmetric_difference():仅在其中一个集合中的元素(并集减去交集)。
运算符支持复合赋值形式:s |= other 等价于 s.update(other),s &= other 等价于 s.intersection_update(other) 等。
方法形式的运算符可以接受任意可迭代对象作为参数,而运算符形式要求右侧必须是 set 类型。
【记忆锚点】 “& 交 | 并 - 差去,^ 对称各一半。”
【易混对比】
| 运算符 | 方法 | 含义 | 示例结果 |
|---|---|---|---|
| & | intersection | 交集 | {1,2,3} & {2,3,4} = |
| | | union | 并集 | {1,2,3} | {2,3,4} = |
| - | difference | 差集 | {1,2,3} - {2,3,4} = |
| ^ | symmetric_difference | 对称差 | {1,2,3} ^ {2,3,4} = |
换问法:{1, 2, 3} - {2, 3, 4} 的结果是什么?答:{1},差集保留左侧集合独有的元素。
【自测】
a = {1, 2, 3}
b = {2, 3, 4}
print(a ^ b)答:{1, 4}。与第 P31 题连考。
【知识关联】
- 同库关联:与 P29;并交差对称差运算符 | & - ^。
- 实现层:集合运算 C 实现高效;也可用方法 form。
- 面试追问:① 可变与不可变集合运算?② 子集判断?
【拓展延伸】
- 变式问法:
{1,2} & {2,3};issubset。 - 版本差异:语义稳定。
- 工程注意点:标签过滤用交集;权限集合运算清晰。
P31 · 知识点:集合去重
【题目】list(set([1, 2, 2, 3, 3, 3])) 的结果是?
A. {1, 2, 3} B. [1, 2, 2, 3, 3, 3] C. 抛异常 D. [1, 2, 3]
答案:D
【考点】用 set 去重后转回列表。
【结论】选 D。利用集合自动去重后转回列表,得到 [1, 2, 3]。
【推导过程】
[1, 2, 2, 3, 3, 3] # 原始列表
set([1, 2, 2, 3, 3, 3]) # 转为集合去重 → {1, 2, 3}
list({1, 2, 3}) # 转回列表 → [1, 2, 3]【逐项辨析】 D. [1, 2, 3]:正确。先转 set 去重,再转 list 恢复列表类型。 B. [1, 2, 2, 3, 3, 3]:错误。set() 会去除所有重复元素。 C. 抛异常:错误。列表元素均为整数(可哈希),可正常创建集合。 A. {1, 2, 3}:错误。最外层调用了 list(),结果已转回列表,不是集合。
【知识点】 集合去重是 Python 中最简洁的去重方式之一:
- set(可迭代对象):利用哈希表去重,时间复杂度 O(n)。
- 转回 list:list(set(...)) 恢复列表类型。
关键注意事项:由于集合无序,去重后元素顺序不保证与原始顺序一致。对整数小范围场景通常恰好有序,但程序不应依赖该顺序。
若需保序去重,Python 3.7+ 推荐使用 dict.fromkeys():list(dict.fromkeys([1, 2, 2, 3])),既保持首次出现顺序,又具有 O(n) 时间复杂度。
【记忆锚点】 “去重两步走:先 set 再 list,顺序不保要留神。”
【易混对比】
| 去重方式 | 代码 | 是否保序 | 时间复杂度 |
|---|---|---|---|
| set 转 list | list(set(lst)) | 否 | O(n) |
| dict.fromkeys | list(dict.fromkeys(lst)) | 是(3.7+) | O(n) |
| 遍历+判断 | 循环 + if x not in res | 是 | O(n²) |
换问法:list(set([3, 1, 2])) 的结果一定是 [1, 2, 3] 吗?答:不一定,集合无序,任何顺序都可能出现,程序不应依赖顺序。
【自测】
lst = [1, 1, 2, 2, 3, 3]
print(list(dict.fromkeys(lst)))答:[1, 2, 3]。dict.fromkeys() 是保序去重的优雅写法,Python 3.7+ 有效。与第 P32 题连考。
【知识关联】
- 同库关联:与 P29;
list(set(L))不保序。 - 实现层:哈希去重;dict.fromkeys 可保序去重(3.7+)。
- 面试追问:① 如何去重且保序?② 不可哈希元素?
【拓展延伸】
- 变式问法:列表含 dict 去重要用别的方式(json 化或 key 函数)。
- 版本差异:dict 有序后 fromkeys 成为常用保序去重。
- 工程注意点:大数据去重 set;展示顺序注意。
P32 · 知识点:frozenset
【题目】frozenset 与 set 的区别是?
A. frozenset 可变 B. frozenset 是有序的 C. 两者没有任何区别 D. frozenset 不可变(可哈希),因此可以作为字典的键
答案:D
【考点】不可变集合 frozenset 的用途。
【结论】选 D。frozenset 是不可变集合,可哈希,因此能作为字典的键或集合的元素。
【逐项辨析】 A. frozenset 可变:错误。frozenset 是不可变的,创建后无法修改。 D. frozenset 不可变(可哈希),因此可以作为字典的键:正确。不可变性带来可哈希性,这是其核心价值。 C. 两者没有任何区别:错误。可变性是 set 与 frozenset 的本质区别。 B. frozenset 是有序的:错误。frozenset 与 set 一样都是无序容器。
【知识点】 frozenset 与 set 的核心区别:
- 可变性:set 可变,支持 add、remove、discard 等修改操作;frozenset 不可变,创建后内容固定。
- 可哈希性:frozenset 可哈希,set 不可哈希。
- 用途差异:frozenset 可作为字典键、其他集合的元素;set 因不可哈希,既不能作字典键,也不能嵌套进另一个 set。
两者共同点:均无序、元素唯一、元素必须可哈希,底层均为哈希表实现,成员检测复杂度 O(1)。
【记忆锚点】 “frozen 冻住不能改,哈希合格当键来。”
【易混对比】
| 特性 | set | frozenset |
|---|---|---|
| 可变 | 是 | 否 |
| 可哈希 | 否 | 是 |
| 支持 add / remove | 是 | 否 |
| 可作字典键 | 否 | 是 |
| 可作集合元素 | 否 | 是 |
| 无序 / 唯一 | 是 | 是 |
换问法:s = frozenset([1, 2]); d = {s: "value"} 是否合法?答:合法,frozenset 可哈希,可作为字典键。
【自测】
s1 = {1, 2}
s2 = frozenset([1, 2])
print(hash(s2))
# print(hash(s1)) # 这行会怎样?答:输出 s2 的哈希值;hash(s1) 会抛出 TypeError: unhashable type: 'set',因为 set 不可哈希。与第 P23 题连考。
【知识关联】
- 同库关联:与 P29/P24;不可变集合作为 dict key 或放入 set。
- 实现层:frozenset 可哈希。
- 面试追问:① 何时用 frozenset?② 与 tuple 表示集合的差异?
【拓展延伸】
- 变式问法:
{frozenset({1,2}): "x"}。 - 版本差异:语义稳定。
- 工程注意点:权限位/标签组需要哈希时用 frozenset。