元组
为什么有了列表,还需要元组
上一节我们学了列表。列表很好用,能装很多值,还能随时增删改:
l = [1, 2, 3]
l.append(4)
l[0] = 100
print(l)
[100, 2, 3, 4]
方便是方便,可是“什么都能改”有时候反而是个问题。
想一个场景:程序要记录一个点的坐标。坐标是 (3, 5),它天生就是两个数绑在一起,不应该被谁随手改掉一个。再比如一年十二个月的名字、一周七天的名字、一个身份证上的出生日期,这些东西一旦定下来,就是固定的,改了就错了。
如果这些都用列表存,别人(或者几个月后的你自己)看到 l[0] = 100,程序不会拦着,错误就这么悄悄发生了。
我们希望有一种东西:
- 和列表一样,能把多个值打包成一个整体
- 有顺序,可以用下标、切片、
for遍历 - 但是创建以后就不能改,谁想改就报错
这就是元组(tuple)。
一句话概括:列表是“可变的清单”,元组是“定下来的记录”。 需要增删改,用列表;一旦确定就不该再动,用元组。
创建元组
元组用小括号 () 表示,元素之间用逗号隔开:
t1 = (1, 2, 3, "a")
print(t1)
(1, 2, 3, 'a')
和列表一样,里面可以放数字、字符串,也可以混着放。
只有一个元素时的坑
只放一个元素的元组,必须写成 (1,),后面的逗号不能省:
a = (1,)
b = (1)
print(type(a))
print(type(b))
<class 'tuple'>
<class 'int'>
(1) 只是普通的括号,Python 看成数字 1;只有加上逗号 (1,),才表示“这是一个只有一个元素的元组”。这个逗号是元组的标志,不是可有可无的。
其实,真正决定是不是元组的是逗号,不是括号:
t = 1, 2, 3
print(type(t))
print(t)
<class 'tuple'>
(1, 2, 3)
括号只是让代码更好看、更清楚。写元组时建议还是加上括号。
访问元素:和列表一样
元组支持列表那一套下标和切片:
t1 = (1, 2, 3, "a")
print(f"t1[0]: {t1[0]}")
print(f"t1[-1]: {t1[-1]}")
print(f"t1[2:4]: {t1[2:4]}")
t1[0]: 1
t1[-1]: a
t1[2:4]: (3, 'a')
f"..." 是 f-string,花括号里的表达式会被算出来再拼进字符串,这是 Python 3.12 里最常用的格式化写法。
注意 t1[2:4] 的结果是 (3, 'a'),切片的类型和原来一样:列表切片得到列表,元组切片得到元组。
元组也可以用 + 拼接、用 len 看长度、用 for 遍历、用 in 判断存在:
t1 = (1, 2, 3, "a")
t2 = (3, 4)
t3 = t1[:2] + t2
print(t3)
for x in t3:
print(x)
print(len(t3))
print(2 in t3)
(1, 2, 3, 4)
1
2
3
4
4
True
不能改:元组的核心特点
元组和列表最大的区别,就是创建以后不能修改。
不能改某个位置
t = (1, 2)
t[0] = "1"
这会报 TypeError:
TypeError: 'tuple' object does not support item assignment
报错信息直译过来就是“元组对象不支持给某一项赋值”。列表能做的这件事,元组直接拒绝。
不能追加、不能删除元素
列表有 append、remove、pop,元组没有这些方法:
t = (1, 2, 3)
t.append(4)
AttributeError: 'tuple' object has no attribute 'append'
AttributeError 的意思是“元组对象没有 append 这个属性”,也就是根本没提供这个方法。
可以删掉整个变量
元组里的元素不能删,但可以删掉整个变量:
t = (1,)
del t
print(t)
NameError: name 't' is not defined
del t 删的是变量名 t 本身,不是元组里的元素。删完再访问 t,就报 NameError。
列表和元组互相转换
用 tuple() 把列表转成元组,用 list() 把元组转成列表:
l = [1, 2, 3]
print(type(tuple(l)))
t = (1, 2, 3)
print(type(list(t)))
<class 'tuple'>
<class 'list'>
这个转换很有用。比如接口返回的数据是元组,你想改一改,就先 list() 转成列表,改完再 tuple() 转回去。
Python 3 里类型显示是 <class 'tuple'>,不再是 Python 2 的 <type 'tuple'>。
元组解包:最常用的写法
元组有一个很好用的特性叫解包(unpacking):把元组里的值一次分给几个变量。
point = (3, 5)
x, y = point
print(x)
print(y)
3
5
左边几个变量,右边元组几个值,一一对应。要的数量对不上会报错:
point = (3, 5)
x, y, z = point
ValueError: not enough values to unpack (expected 3, got 2)
这个写法在“函数返回多个值”时天天用:
def min_max(nums):
return min(nums), max(nums)
low, high = min_max([3, 1, 4, 1, 5])
print(low, high)
1 5
表面看函数返回了两个值,其实是返回了一个元组 (1, 5),然后在调用处解包给 low 和 high。所以理解元组,也是理解 Python 函数返回值的关键。
元组真的完全不能改吗
有一种情况要小心。如果元组里装的是列表,那个列表本身还是可以改的:
t = (1, [2, 3])
t[1].append(4)
print(t)
(1, [2, 3, 4])
这里没有改元组的位置(t[1] 始终指向同一个列表),改的是那个列表内部的内容。所谓“元组不可变”,指的是元组里每一项的指向不变,并不保证每一项内部不再变化。这条先了解,遇到再回来看。
什么时候用列表,什么时候用元组
| 列表 list | 元组 tuple | |
|---|---|---|
| 写法 | [1, 2, 3] |
(1, 2, 3) |
| 能否修改 | 能增删改 | 创建后不能改 |
| 下标 / 切片 | 支持 | 支持 |
| 常用方法 | append、remove 等一堆 | 几乎没有 |
| 适合场景 | 会变化的一组数据 | 定下来的记录 |
判断标准很简单:这份数据以后会不会变?
- 会变:一个班的学生名单(有人转学、有人加入)、购物车里的商品——用列表
- 不变:坐标、日期、RGB 颜色、一年十二个月——用元组
另外,元组因为不可变,可以当作字典的键,列表不行(键必须不可变)。这一点学到字典时会再遇到。
小结
- 元组和列表都是“有顺序的一组值”,写法用
() - 元组创建后不能改:不能改元素、不能追加、不能删除
- 只有一个元素要写
(1,),逗号是关键 - 下标、切片、
+、len、for、in的用法和列表一样 - 切片的结果类型不变:元组切片还是元组
- 用
tuple()/list()在两者之间转换 - 解包
x, y = point是元组最常用的写法,也是函数返回多值的基础 - 选择依据:数据会变用列表,定下来不变用元组
用 python examples/tuple.py 运行本节的例子。