Python 中 frozenset 不可变集合
python
从列表创建
fs1 = frozenset([1, 2, 3, 2]) print(fs1) # frozenset({1, 2, 3})
从字符串创建(每个字符成为一个元素)
fs2 = frozenset("hello") print(fs2) # frozenset({'h', 'e', 'l', 'o'})
从元组创建
fs3 = frozenset((4, 5, 6)) print(fs3) # frozenset({4, 5, 6})
### 从其他集合创建
可以从普通 `set` 或另一个 `frozenset` 创建,效果等同于复制一份冻结后的集合。
```python
s = {1, 2, 3}
fs = frozenset(s)
print(fs) # frozenset({1, 2, 3})
another_fs = frozenset(fs)
print(another_fs) # frozenset({1, 2, 3})
创建空 frozenset
不传入任何参数即可创建空的不可变集合。
empty = frozenset()
print(empty) # frozenset()
frozenset 与 set 的核心区别
| 特性 | set (可变集合) | frozenset (不可变集合) |
|---|---|---|
| 可变性 | 可以增、删、改元素 | 创建后不能修改任何元素 |
| 可哈希 | 否(不能作为字典键或集合元素) | 是(可作为字典键或集合元素) |
| 支持的方法 | 所有集合操作方法(包括修改) | 仅支持不修改集合的方法 |
| 适用场景 | 日常数据去重、成员测试等 | 常量集合、复合键、多值映射键等 |
需要特别注意:frozenset 没有 add()、remove()、discard()、pop()、clear() 和 update() 等方法,调用这些方法会直接触发 AttributeError。
fs = frozenset([1, 2, 3])
# 以下操作均会报错
# fs.add(4)
# fs.remove(1)
不可变集合支持的操作
虽然不能修改,但 frozenset 完美继承了所有不改变集合的查询、比较和集合运算方法。
查询与测试
len(fs):获取元素个数。x in fs/x not in fs:成员检测。isdisjoint(other):两个集合没有共同元素时返回True。
fs = frozenset([1, 2, 3])
print(len(fs)) # 3
print(2 in fs) # True
print(fs.isdisjoint({4, 5})) # True
比较操作
支持所有比较运算符,用来检查子集、超集、相等关系。
issubset(other)或fs <= other:当前集合是另一个集合的子集。issuperset(other)或fs >= other:当前集合是另一个集合的超集。==、!=:比较两个集合是否包含完全相同的元素。
a = frozenset([1, 2])
b = frozenset([1, 2, 3])
print(a.issubset(b)) # True
print(b.issuperset(a)) # True
print(a < b) # True (真子集)
print(a == b) # False
集合运算(返回新的 frozenset 或 set)
所有数学集合运算都会产生一个新的集合。如果参与运算的双方都是 frozenset,结果也是 frozenset;如果一方是普通 set,则结果类型通常是左操作数的类型。
- 并集:
fs | other或fs.union(other) - 交集:
fs & other或fs.intersection(other) - 差集:
fs - other或fs.difference(other) - 对称差集:
fs ^ other或fs.symmetric_difference(other)
fs1 = frozenset([1, 2, 3, 4])
fs2 = frozenset([3, 4, 5, 6])
print(fs1 | fs2) # frozenset({1, 2, 3, 4, 5, 6})
print(fs1 & fs2) # frozenset({3, 4})
print(fs1 - fs2) # frozenset({1, 2})
print(fs1 ^ fs2) # frozenset({1, 2, 5, 6})
# 与普通 set 运算
s = {3, 4, 5}
print(fs1.union(s)) # frozenset({1, 2, 3, 4, 5}) 左操作数为 frozenset
print(s.union(fs1)) # {1, 2, 3, 4, 5} 左操作数为 set
转换为其他类型
frozenset 可以转换为列表、元组或普通 set。
fs = frozenset([3, 1, 2])
print(list(fs)) # [1, 2, 3](顺序不固定)
print(tuple(fs)) # (1, 2, 3)
print(set(fs)) # {1, 2, 3}
可哈希性与作为字典键
因为 frozenset 是不可变且可哈希的,它可以被用作字典的键或另一个集合的元素。这是它与 set 最大的实践差异。
# frozenset 作为字典键
d = {}
key_one = frozenset([1, 2])
key_two = frozenset([3, 4])
d[key_one] = "group A"
d[key_two] = "group B"
print(d) # {frozenset({1, 2}): 'group A', frozenset({3, 4}): 'group B'}
# frozenset 作为集合的元素
nested_set = {frozenset([1]), frozenset([2, 3])}
print(nested_set) # {frozenset({1}), frozenset({2, 3})}
这种特性在需要将一组值当作不可变的组合标识符时非常有用,例如图算法中的边集、多条件索引等。
常见使用场景
1. 缓存或配置中的常量集合
当某个集合在整个程序生命周期内不应改变时,用 frozenset 可以防止意外修改并明确表达不可变的意图。
# 常量:有效的状态码
VALID_STATUS = frozenset({200, 201, 204})
def is_success(status_code):
return status_code in VALID_STATUS
2. 多值映射的复合键
用一个不可变的集合作为字典键,表示“包含这些元素的组合”所对应的值。
# 记录玩家拥有的两件装备组成的套装效果
equip_bonus = {
frozenset(["sword", "shield"]): "+10 defense",
frozenset(["sword", "helmet"]): "+8 attack",
}
player_equipment = frozenset(["sword", "shield"])
print(equip_bonus.get(player_equipment, "No bonus")) # +10 defense
3. 保证函数参数不被修改
如果一个函数接收一个集合作为参数,但不会也不应修改它,可以在函数内部将其转换为 frozenset,或者直接要求调用者传入 frozenset,以避免意外改动。
def process_tags(tags):
# 确保内部使用的集合不会被篡改
frozen = frozenset(tags)
# ... 后续处理
注意事项与常见陷阱
- 不可变是指集合本身,不是指元素。如果
frozenset包含可变对象(如列表),仍然可以修改这些对象的内容,但这会破坏frozenset的哈希一致性,应避免这样做。实际上,更推荐在frozenset中存放可哈希的不可变元素(数字、字符串、元组等)。 - 结果类型取决于操作符。当
frozenset和set混合使用运算符时,返回的类型是左操作数的类型,这可能会让初学者感到困惑。建议统一使用显式的方法调用(如union())并注意文档说明。 - 空的
frozenset()是真值还是假值? 空的frozenset在布尔上下文中为False,非空为True。这与普通集合规则一致。
if frozenset():
print("非空")
else:
print("空") # 输出:空