Python元组(tuple)是有序、不可变的序列。外形上像用小括号写的列表,读下标、做切片的方式和列表几乎一样,但建好之后不能改其中的元素。坐标点、一组配置常量、函数一次交出来的多个返回值,这类数据更适合放进元组,减少被中途改掉的可能。

先建一个元组

最常见的写法是圆括号加逗号分隔。下面这个元组有三个整数,可以用下标读出某一项,也可以用 len 看长度:

t = (1, 2, 3)
print(t)          # (1, 2, 3)
print(t[0])       # 1
print(len(t))     # 3

元素不必同一类型,字符串、浮点数可以和整数放在一起。空元组写成 ()。只有一个元素时,逗号不能省:圆括号在表达式里本来就能当分组用,所以 (5) 算出来还是整数 5,必须写成 (5,) 解释器才认作元组。打包时甚至可以省略外层括号,逗号才是关键:

a = ()
b = (5,)
c = (1, "hello", 3.5)
d = 1, 2, 3

print(b, d)       # (5,) (1, 2, 3)

不可变意味着什么

试图给某个下标重新赋值,或调用列表才有的 append,都会直接报错。元组没有原地修改这一套接口:

t = (1, 2, 3)
# t[0] = 99    # TypeError
# t.append(4)  # AttributeError

真要改内容,常规做法是先 list(t) 转成列表,改完再用 tuple(...) 转回去,得到的是一份新元组,原来的 t 并没有被原地改写。

还有一层容易误会:不可变约束的是元组自己的槽位,不是槽位里指向的对象。若某个位置放的是列表,你仍可以往那个列表里 append,元组的长度和各位置指向谁没变,变的是列表内部。需要整份数据都锁死,就不要往元组里塞可变对象。

下标与切片

正下标从左边 0 起算,负下标从右边数,-1 是最后一项。切片同样是含头不含尾,步长写成负数还能倒序取出一个新元组:

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)

切片得到的也是元组。因为不能对切片赋值来替换一段,这点和列表不同。

元组解包

左侧写多个名字、右侧给一个元组,解释器会按位置一一对应,这叫解包。交换两个变量时,右侧其实先打包成元组再解到左侧,所以能写成一行。函数里 return 3, 5 返回的就是元组,调用方照样解包接收:

point = (3, 5)
x, y = point
print(x, y)        # 3 5

a, b = b, a
print(a, b)

def get_point():
    return 3, 5

x, y = get_point()
print(x, y)        # 3 5

左右两边的个数必须一致,否则会报 ValueError。元素很多、只想抓头尾时,可以用 * 收剩余项,例如 first, *mid, last = t

遍历与成员判断

for 可以直接遍历元组里的每一项。in 判断某个值是否出现,count / index 则分别统计次数和查找首次下标,用法和列表同名方法接近:

t = ("a", "b", "c")
for item in t:
    print(item, end=" ")   # a b c
print()

print("b" in t)      # True
print(t.count("a"))  # 1
print(t.index("c"))  # 2

元组和列表怎么选

特点列表 list元组 tuple
可变性可变不可变
语法中括号 []小括号 () 或省略
用途数据还会增删改当作固定记录
性能与键稍慢稍快,且可作字典键

选型可以记一条经验:还要往里追加、删除,就用列表。内容在逻辑上是一条写死的记录,或要拿去当字典的键,就用元组。元组可哈希,列表不可哈希,这是能不能当键的根本原因。

常见误区

  • 单元素忘了逗号时,(5) 仍是整数,不是元组。
  • 内容要调整时,先转列表再转回元组,而不是对下标硬赋值。
  • 元组里如果嵌了列表,内部列表仍可变,锁住的只是元组自身那一层结构。