Skip to content

元组

为什么有了列表,还需要元组

上一节我们学了列表。列表很好用,能装很多值,还能随时增删改:

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 运行本节的例子。