Python 元组
元组(tuple)是列表的"亲兄弟"——几乎一模一样,只有一个关键区别:元组不可变(创建后不能增删改)。这听起来像是个限制,实际上却非常有用。这一章我们看为什么 Python 要有这个"残缺版列表"。
1. 什么是元组?
元组是有序、不可变的序列,用圆括号 () 创建。它和列表一样支持索引、切片、遍历、解包,但不能修改元素。
# 元组:有序、不可变的序列,用圆括号 () 创建
point = (3, 4)
colors = ("red", "green", "blue")
empty = ()
single = (42,) # ⚠ 单元素元组必须加逗号!
# 不加括号也能创建(叫"元组打包")
t = 1, 2, 3
print(t) # (1, 2, 3)
print(type(t)) # <class 'tuple'>
# 用 tuple() 转换
print(tuple([1, 2, 3])) # (1, 2, 3) 列表转元组
print(tuple("hello")) # ('h', 'e', 'l', 'l', 'o')
print(tuple(range(5))) # (0, 1, 2, 3, 4)
# 单元素元组的陷阱!
x = (5) # 这不是元组,就是整数 5
y = (5,) # 这才是单元素元组
print(type(x)) # <class 'int'>
print(type(y)) # <class 'tuple'>单元素元组的陷阱:(5) 只是数字 5(小括号被当成数学分组),(5,) 才是元组。新手常踩坑,务必记住加逗号。
2. 元组的不可变性
t = (1, 2, 3)
# 1. 索引和切片,和列表完全一样
print(t[0]) # 1
print(t[-1]) # 3
print(t[1:]) # (2, 3)
print(len(t)) # 3
# 2. 不可变!不能改、不能加、不能删
# t[0] = 99 # ❌ TypeError: 'tuple' object does not support item assignment
# t.append(4) # ❌ AttributeError: tuple 没有 append
# del t[0] # ❌ TypeError
# 3. 但可以整个重新赋值(创建新元组)
t = (4, 5, 6) # t 指向新元组,旧的 (1,2,3) 被回收
print(t) # (4, 5, 6)
# 4. 元组里的"可变元素"仍然可以改
nested = (1, [2, 3], 4)
nested[1].append(99) # 改的是列表,不是元组本身
print(nested) # (1, [2, 3, 99], 4)
# 5. 元组的运算:+ 拼接,* 重复,和列表一样
print((1, 2) + (3, 4)) # (1, 2, 3, 4)
print((1, 2) * 3) # (1, 2, 1, 2, 1, 2)"不可变"的含义:
- 不能
t[i] = x改元素。 - 不能
append/insert/remove。 - 但可以整个重新赋值(创建新元组)。
- 如果元素本身是可变的(如列表),那个元素内部仍可改——这不算"修改元组"。
3. 打包与解包
这是元组最常用的特性。把多个值放进元组叫"打包",反过来叫"解包"。
# 元组解包(tuple unpacking),Python 强大的特性之一
# 1. 基本解包
point = (3, 4, 5)
x, y, z = point
print(x, y, z) # 3 4 5
# 2. 值交换(经典 Python 写法)
a, b = 1, 2
a, b = b, a
print(a, b) # 2 1
# 3. 用 * 收集多余元素
first, *rest = (1, 2, 3, 4, 5)
print(first) # 1
print(rest) # [2, 3, 4, 5] 注意是列表
*init, last = (1, 2, 3, 4, 5)
print(init) # [1, 2, 3, 4]
print(last) # 5
first, *middle, last = (1, 2, 3, 4, 5)
print(middle) # [2, 3, 4]
# 4. 忽略某些值(用 _ 约定)
name, _, age = ("小明", "男", 20) # 不要性别
print(name, age) # 小明 20
# 5. 遍历键值对时解包(超常用)
user = {"name": "小明", "age": 20}
for key, value in user.items():
print(f"{key}: {value}")解包是 Python 的优雅利器:
a, b = b, a一行交换变量(其他语言要 3 行)。- 用
*收集多余元素成列表。 - 用
_忽略不需要的值。 - 遍历字典
items()时几乎必用。
4. 函数返回多个值(经典用法)
很多语言函数只能返回一个值,要返回多个得封装成对象或结构体。Python 借助元组,优雅地返回多个值。
# 元组最常见用途:函数返回多个值
def min_max(numbers):
"""同时返回最小值和最大值"""
return min(numbers), max(numbers) # 返回元组(省略了括号)
result = min_max([3, 1, 4, 1, 5, 9])
print(result) # (1, 9)
print(type(result)) # <class 'tuple'>
# 直接解包接收(更优雅)
low, high = min_max([3, 1, 4, 1, 5, 9])
print(f"最小:{low},最大:{high}")
# 输出: 最小:1,最大:9
# 另一个例子:返回状态和数据
def get_user(user_id):
if user_id == 1:
return True, {"name": "小明"} # 成功
return False, None # 失败
ok, data = get_user(1)
if ok:
print(f"找到用户:{data['name']}")
# 输出: 找到用户:小明这种 return True, data 或 return ok, value 的写法在 Python 标准库里随处可见,是 Pythonic 风格的重要部分。
5. 命名元组 namedtuple
普通元组用下标访问(p[0]),可读性差。命名元组给字段起名字,既能像对象一样 p.x 访问,又保持元组的轻量和不可变。
# 命名元组 namedtuple:给元组字段起名字,更易读
from collections import namedtuple
# 创建一个"点"类型
Point = namedtuple("Point", ["x", "y"])
p = Point(3, 4)
# 既能用名字访问,也能用索引访问
print(p.x, p.y) # 3 4 <- 用名字(推荐,可读)
print(p[0], p[1]) # 3 4 <- 用索引
# 解包也支持
x, y = p
print(x, y) # 3 4
# 实用例子:表示一条记录
Student = namedtuple("Student", ["name", "age", "score"])
students = [
Student("小明", 20, 85),
Student("小红", 19, 92),
Student("小刚", 21, 78),
]
for s in students:
print(f"{s.name}, {s.age} 岁,分数 {s.score}")命名元组是简单数据类的绝佳选择——比普通类写法少,比字典更结构化,而且不可变(更安全)。处理 CSV、数据库记录时尤其好用。
6. 元组 vs 列表:什么时候用哪个?
既然列表能干所有事,为什么还要元组?有几个理由:
- 不可变 = 更安全:元组保证数据不会被意外修改,适合存"不应变"的数据(配置、坐标、常量)。
- 可哈希 = 能当字典 key:列表不能做字典 key,元组可以(只要元素都可哈希)。
- 更省内存:元组比同等列表占用内存少(因为没有动态扩容的开销)。
- 语义清晰:用元组表示"固定长度的记录"(如 (x, y) 坐标、(name, age) 记录),列表表示"变长集合"。
经验法则:数据可能变,用列表;数据不会变,用元组。
7. 元组作为字典 key
# 元组能做字典的 key,列表不行
locations = {
(31, 121): "上海",
(39, 116): "北京",
(23, 113): "广州",
}
print(locations[(31, 121)]) # 上海
# 列表不能做 key(因为可变、不可哈希)
# d = {[1, 2]: "x"} # ❌ TypeError: unhashable type: 'list'小结
元组是"不可变列表"。重点掌握:创建(注意单元素要加逗号)、不可变(但元素可变则内部可改)、解包(Python 优雅的核心,值交换、多返回值)、命名元组(轻量数据类)、何时用元组(不可变、可哈希、省内存、语义清晰)。下一篇看字典——Python 里处理键值数据的利器。
← 上一篇 Python 列表
下一篇 Python 字典 →