初学者常问:”元组不就是不能改的列表吗?为什么 Python 还要专门搞一个?” 简短答案:元组不是”列表的阉割版”,它有自己的核心用途——表示不可变的记录。列表适合”一堆同类型的东西”,元组适合”一件事的多个字段”。学完这一篇,你会理解为什么函数返回多值用元组、为什么字典 key 可以是元组、为什么大量 Python 代码里都是圆括号包起来的一小串数据。
一、创建元组
元组用小括号:
1 | empty = () |
只有一个元素的元组必须带逗号,否则 Python 会当成”括号 + 表达式”:
1 | a = (1) # a 是整数 1 |
tuple() 构造函数
1 | tuple("abc") # ('a', 'b', 'c') |
二、索引与切片
元组和列表在读取上几乎一样:
1 | t = (10, 20, 30, 40, 50) |
区别在于:
1 | t[0] = 99 # ❌ TypeError: 'tuple' object does not support item assignment |
元组不可变——一旦创建,不能修改、不能增加、不能删除元素。
三、元组的常用方法
元组的方法只有两个:
1 | t = (1, 2, 3, 2, 1) |
对比列表 30+ 个方法,元组显得非常”精简”,这正是它的定位——尽量减少 API,只做”记录”用。
四、元组的核心用途
1. 表达一条记录(结构化数据)
1 | # 一个点是 (x, y) |
这种”轻量记录”场景,元组比列表更合适——语义是”一个整体”,字段个数固定,顺序有意义。
2. 多值返回
Python 函数返回多值本质上就是返回元组:
1 | def divmod_(a, b): |
不用 return q, r 打包成元组,再解包,一步到位。
3. 作为字典的 key
元组不可变,所以可哈希,能做 dict 的 key(列表不行):
1 | # 二维网格的位置 → 值 |
4. 作为集合元素
同理,元组能进 set:
1 | seen = {("a", 1), ("b", 2)} |
5. 函数参数打包/解包
1 | def sum_all(*args): # args 是元组 |
五、序列解包
元组和”解包赋值”是最佳搭档:
1 | # 简单解包 |
扩展解包(Python 3.0+)
1 | first, *rest = [1, 2, 3, 4] # first=1, rest=[2, 3, 4] |
*rest 收集剩余的元素成为列表(不是元组,注意)。
忽略部分值
1 | _, _, age = user # 只想要 age |
_ 只是约定俗成的”我不关心这个变量”,本身还是一个普通变量。
六、元组不可变 ≠ 内容不可变
这是新手最容易误解的一点:
1 | t = ([1, 2], [3, 4]) |
元组的”不可变”只保证内部指针不换,指向的对象本身如果是可变对象,仍然可以被修改。要真正的完全不可变,用 frozenset 或者 namedtuple 存不可变元素。
七、namedtuple:给元组的字段起名字
用元组的最大痛点:字段是按位置访问的(user[1]),代码可读性差。collections.namedtuple 解决这个问题:
1 | from collections import namedtuple |
namedtuple 的优点:
- 不可变(继承了元组);
- 有名字(比裸元组可读得多);
- 兼容元组(旧代码不用改);
- 内存开销小(跟普通元组一样,比 class 省内存)。
Python 3.6+ 还有更强大的 typing.NamedTuple:
1 | from typing import NamedTuple |
八、元组 vs 列表:什么时候用哪个
| 场景 | 首选 |
|---|---|
| 一堆同类型元素,数量会变 | list |
| 一条固定字段的记录 | tuple / namedtuple |
| 函数返回多个值 | tuple |
| 作为 dict 的 key、set 的元素 | tuple |
| 数据在整个生命周期不变 | tuple |
| 需要 append / sort / 反复修改 | list |
心法:列表是同质异量,元组是异质同量。
九、性能对比
- 元组比列表创建更快、占内存更小(列表要额外维护容量);
- 迭代速度差不多;
- 元组可缓存哈希,可以做 dict key;
- 元组一般更适合作为常量数据。
1 | import sys |
(具体数字随平台变,趋势不变。)
十、一个综合例子:坐标聚类
1 | # clustering.py |
代码用到了 namedtuple、元组做 dict key、defaultdict 等特性。
十一、常见陷阱
- 单元素元组必须加逗号:
(1)是整数,(1,)才是元组; - 元组不可变但可以嵌套可变对象;
+和*会创建新元组(因为元组不能就地修改);t += (x,)也是创建新元组,如果原元组被别的名字引用,那个名字看到的还是旧元组;- 括号有时会被当成分组符:
(1)不是元组,别忘了逗号; - 元组不能 sort:
sorted(tuple)返回列表,不是元组;要保留元组:tuple(sorted(t))。
十二、小结与延伸阅读
- 元组用小括号(可省略),单元素必须加逗号;
- 元组不可变,索引/切片只读;
- 核心用途:结构化记录、多值返回、字典 key、扩展解包;
- 元组不可变 ≠ 内部对象不可变;
- 需要给字段起名字用 namedtuple / typing.NamedTuple;
- 数据不需要变就用元组,需要 append/sort 就用列表。
延伸阅读:
- 官方 tuple 文档:https://docs.python.org/zh-cn/3/tutorial/datastructures.html#tuples-and-sequences
- namedtuple 文档:https://docs.python.org/zh-cn/3/library/collections.html#collections.namedtuple
下一篇 字典 Dict 的使用 我们讲 Python 里另一个”半壁江山”的容器——字典。