Python 元组、集合与不可变性

AI 辅助生成本文由 AI 起草并经人工核查,示例均标明软件版本;发现错误请以官方文档为准。

Python 3.13

列表和字典用得最多,但另外两种容器能省下很多代码:元组表达「固定结构」,集合解决「去重与归属判断」。

环境准备

示例在 Python 3.13 下验证,直接在 REPL 里试即可。

元组:固定结构的小记录

point = (3, 4)
x, y = point        # 解包
print(x, y)         # 3 4

括号常常可以省略,真正定义元组的是逗号:

a = 1, 2            # (1, 2)
single = (1,)       # 单元素必须带逗号
not_a_tuple = (1)   # 这是整数 1,不是元组

打包与解包

name, age = "小明", 18

first, *rest = [1, 2, 3, 4]   # first=1, rest=[2, 3, 4]
a, b = b, a                   # 交换变量,不需要临时变量

函数返回多个值,本质就是返回一个元组:

def min_max(values):
    return min(values), max(values)

low, high = min_max([3, 1, 9])

遍历字典的 items() 拿到的也是元组:

for key, value in {"a": 1, "b": 2}.items():
    print(key, value)

具名元组

下标 point[0] 可读性差,用 NamedTuple 给字段起名:

from typing import NamedTuple

class Point(NamedTuple):
    x: float
    y: float

p = Point(3, 4)
p.x              # 3
p._asdict()      # {'x': 3, 'y': 4}

它仍是元组:可解包、可比较、可当字典键。

不可变性到底是什么意思

元组本身不能增删元素,但里面的可变对象仍能改:

record = (1, [2, 3])
record[1].append(4)     # 可以:改的是列表
record[1] = [2, 3, 4]   # TypeError:不能换掉元素

可哈希性(能不能当字典键)取决于所有元素都可哈希:

{(1, 2): "ok"}          # 可以
{(1, [2]): "ok"}        # TypeError: unhashable type: 'list'

集合:去重与归属判断

tags = {"python", "web", "python"}
print(tags)             # {'python', 'web'},重复自动去掉

集合无序,只能迭代不能按下标取。它的价值在 O(1) 的成员判断:

if "python" in tags:    # 列表是 O(n),集合平均 O(1)
    print("已存在")

增删:

tags.add("cli")
tags.discard("web")     # 不存在也不报错
tags.remove("web")      # 不存在会抛 KeyError

集合运算

a = {"python", "go"}
b = {"go", "rust"}

a | b      # {'python', 'go', 'rust'},并集
a & b      # {'go'},交集
a - b      # {'python'},差集
a ^ b      # {'python', 'rust'},对称差集(只在一边出现)

去重并保持顺序(集合会乱序):

items = ["b", "a", "b", "c"]
list(dict.fromkeys(items))    # ['b', 'a', 'c'],3.7+ 字典保序

需要不可变集合用 frozenset:

frozen = frozenset(["a", "b"])
{frozen: "可当键"}              # 可以

四种容器怎么选

需求 选择 原因
有序、可改、允许重复 list 按下标访问、追加
固定结构、当字典键 tuple / NamedTuple 不可变、可哈希
去重、判断「在不在」 set 成员判断 O(1)
键值映射 dict 按 key 取值

常见错误与注意事项

1. 忘了单元素元组的逗号

("a") 是字符串,("a",) 才是元组。

2. 依赖集合顺序

集合无序,set(["c", "a"])[0] 不可靠,也不要假设迭代顺序。

3. 把列表放进集合或字典键

{["a"]: 1}      # TypeError: unhashable type: 'list'

改用元组或 frozenset。

4. 空集合的写法

{}          # 这是空字典
set()       # 这才是空集合

5. 用元组做「可变默认值」

元组本身不可变,但里面放列表照样会中「可变默认值」的坑(见函数那一篇)。

小结

  • 元组表达固定结构,靠逗号定义,支持解包,可做字典键。
  • 不可变是「元素引用不能换」,不代表内部对象不能改。
  • 集合用于去重与快速成员判断,支持交并差运算,但无序。
  • 选容器先看需求:有序可变用列表,映射用字典,去重归属用集合。

下一步