Python 中 frozenset 不可变集合

FreeGuideOnline 最新 2026-07-07

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 | otherfs.union(other)
  • 交集fs & otherfs.intersection(other)
  • 差集fs - otherfs.difference(other)
  • 对称差集fs ^ otherfs.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 中存放可哈希的不可变元素(数字、字符串、元组等)。
  • 结果类型取决于操作符。当 frozensetset 混合使用运算符时,返回的类型是左操作数的类型,这可能会让初学者感到困惑。建议统一使用显式的方法调用(如 union())并注意文档说明。
  • 空的 frozenset() 是真值还是假值? 空的 frozenset 在布尔上下文中为 False,非空为 True。这与普通集合规则一致。
if frozenset():
    print("非空")
else:
    print("空")  # 输出:空