元组
元组(tuple)与列表非常相似,都是有序序列,支持索引和切片。但元组有一个根本区别:它是不可变的。创建后不能添加、删除或修改任何元素。
创建元组
# 圆括号表示
point = (3, 4)
# 空元组
empty = ()
# 单元素元组:必须加逗号!
single = (5,) # 逗号不能省略
not_tuple = (5) # 这只是整数 5,不是元组
print type(single) # <type 'tuple'>
print type(not_tuple) # <type 'int'>
# 不带括号也可以(元组打包)
t = 1, 2, 3
print type(t) # <type 'tuple'>
单元素元组的逗号是最常见的陷阱。(5) 中的圆括号被解释为数学优先级符号,而非元组构造符。只有 (5,) 才是元组。
索引与切片
元组支持所有序列操作:
t = (10, 20, 30, 40, 50)
print t[0] # 10
print t[-1] # 50
print t[1:4] # (20, 30, 40)
print t[::-1] # (50, 40, 30, 20, 10)
不可变的含义
"不可变"意味着不能修改元组本身,但如果元组包含可变对象(如列表),这些对象的内容仍然可以修改:
# 元组元素不能重新赋值
t = (1, 2, 3)
t[0] = 100 # TypeError: 'tuple' object does not support item assignment
# 但可变对象的内容可以修改
t = ([1, 2], [3, 4])
t[0].append(3) # 合法!修改的是列表,不是元组
print t # ([1, 2, 3], [3, 4])
t[0] = [5, 6] # TypeError!不能替换整个列表
这种"浅不可变"特性让元组可以安全地作为字典键(要求元素可哈希),同时内部又能容纳动态数据。
元组拆包
元组最实用的特性是拆包(unpacking)——把元组的元素一次性赋给多个变量:
point = (3, 4)
x, y = point
print x # 3
print y # 4
# 交换变量(无需临时变量)
a, b = 10, 20
a, b = b, a
print a, b # 20 10
# 右边先打包成元组,再拆包给左边
拆包时变量数必须与元素数匹配:
t = (1, 2, 3)
a, b = t # ValueError: too many values to unpack
a, b, c, d = t # ValueError: need more than 3 values to unpack
扩展拆包(Python 2 不支持)
Python 3 支持 a, *rest = seq 的扩展拆包,Python 2 不支持。在 Python 2 中需要切片:
# Python 2
nums = [1, 2, 3, 4, 5]
first = nums[0]
rest = nums[1:]
# Python 3 可以写:first, *rest = nums
元组作为字典键
因为元组不可变且可哈希,可以作为字典的键:
# 用坐标作为键
locations = {
(0, 0): "origin",
(1, 0): "east",
(0, 1): "north",
}
print locations[(1, 0)] # "east"
列表不能作为字典键,因为它是可变的:
d = {}
d[[1, 2]] = "A" # TypeError: unhashable type: 'list'
元组 vs 列表的选择
| 场景 | 推荐 |
|---|---|
| 数据不需要修改 | 元组 |
| 需要作为字典键 | 元组 |
| 需要保证数据不被意外修改 | 元组 |
| 需要频繁添加/删除元素 | 列表 |
| 需要排序 | 列表 |
# 用元组表示固定结构
def get_user():
return ("Alice", 25, "Engineer") # 姓名、年龄、职业
name, age, job = get_user()
# 用列表表示动态集合
scores = [85, 90, 78]
scores.append(92)
元组的不可变性是一种语义信号:它告诉读者"这组数据是固定的,不要修改"。在团队协作中,这种信号能减少意外修改带来的 bug。