GEEK
← 返回首页

什么是元组(Tuple)及其工作原理?

#随笔

元组(Tuple)是 Python 中一种**有序且不可变**的数据类型

摘要: 元组(Tuple)是 Python 中一种有序且不可变的数据类型,可以用来保存多个数据。它和列表(List)非常相似,同样支持索引、负索引、切片、成员判断和解包,但最大的区别在于:列表可以修改,元组创建后不能修改其中的元素。本文将从元组的创建和访问开始,逐步介绍索引、切片、tuple()in、解包以及 * 操作符等常见用法,并帮助你理解什么时候应该使用元组而不是列表。


什么是元组(Tuple)?

  • 元组(Tuple):Python 中一种用于保存多个数据的有序集合。
  • 有序(Ordered):元组中的元素会按照固定顺序保存,可以通过索引访问。
  • 不可变(Immutable):元组创建完成后,不能直接修改、添加或删除其中的元素。

最常见的元组写法是使用圆括号:

developer = ("Alice", 34, "Rust Developer")

print(developer)
# ('Alice', 34, 'Rust Developer')

这个元组包含三个元素:

"Alice"          → 字符串
34               → 整数
"Rust Developer" → 字符串

元组中的元素可以是不同的数据类型,因此可以同时保存字符串、整数、布尔值等数据。

例如:

developer = ("Alice", 34, "Rust Developer", True)

元组和列表有什么区别?

元组和列表非常相似。

例如:

languages_list = ["Python", "Java", "C++"]
languages_tuple = ("Python", "Java", "C++")

它们都可以:

  • 保存多个元素。
  • 按顺序保存数据。
  • 使用索引访问元素。
  • 使用负索引访问元素。
  • 使用切片获取部分元素。
  • 使用 in 判断元素是否存在。
  • 进行解包。

但两者最重要的区别是:

列表(List)是可变的,元组(Tuple)是不可变的。

特性 列表 list 元组 tuple
是否有序
是否支持索引
是否支持切片
是否可以修改元素
是否可以添加元素
是否可以删除元素
是否可变 ✅ 可变 ❌ 不可变

可以简单理解:

List
 ↓
可以修改

Tuple
 ↓
不能修改

元组为什么是不可变的?

假设我们创建了一个元组:

programming_languages = ("Python", "Java", "C++", "Rust")

如果尝试修改第一个元素:

programming_languages[0] = "JavaScript"

程序就会产生:

TypeError

类似:

TypeError: 'tuple' object does not support item assignment

意思是:

tuple 对象不支持元素赋值。

也就是说,下面这种操作是不允许的:

programming_languages[0] = "JavaScript"

因为:

原来的元组:

("Python", "Java", "C++", "Rust")
      ↑
    不能修改

元组创建完成后,不能直接把其中某个位置的元素替换掉。


如何访问元组中的元素?

元组是有序的,因此可以通过:

索引(Index)

来访问其中的元素。

例如:

developer = ("Alice", 34, "Rust Developer")

它的索引关系是:

索引:   0       1              2
        ↓       ↓              ↓
元素: Alice     34      Rust Developer

访问第一个元素:

print(developer[0])
# Alice

访问第二个元素:

print(developer[1])
# 34

访问第三个元素:

print(developer[2])
# Rust Developer

什么是索引(Index)?

  • 索引(Index):表示元素在序列中的位置编号。

Python 的索引从 0 开始,而不是从 1 开始。

例如:

numbers = (10, 20, 30, 40, 50)

对应关系:

索引 元素
0 10
1 20
2 30
3 40
4 50

所以:

print(numbers[0])
# 10

print(numbers[3])
# 40

记住:

第一个元素的索引是 0


使用负索引访问元组

Python 还支持:

负索引(Negative Index)

负索引从元组的末尾开始计算。

例如:

numbers = (1, 2, 3, 4, 5)

可以表示成:

正索引:    0    1    2    3    4
            ↓    ↓    ↓    ↓    ↓
元素:      1    2    3    4    5
            ↑    ↑    ↑    ↑    ↑
负索引:   -5   -4   -3   -2   -1

因此:

print(numbers[-1])
# 5

表示最后一个元素。

而:

print(numbers[-2])
# 4

表示倒数第二个元素。

负索引在需要从元组末尾访问数据时非常方便。


索引超出范围怎么办?

如果访问一个不存在的索引,Python 会产生:

IndexError

例如:

numbers = (1, 2, 3, 4, 5)

print(numbers[7])

会产生类似错误:

IndexError: tuple index out of range

因为这个元组只有:

索引 0
索引 1
索引 2
索引 3
索引 4

不存在:

索引 7

所以:

访问不存在的索引,就会产生 IndexError


使用 tuple() 创建元组

除了使用圆括号:

numbers = (1, 2, 3)

还可以使用:

tuple()

来创建元组。

tuple() 是 Python 的内置类型构造函数,可以把一个**可迭代对象(Iterable)**转换成元组。

例如:

developer = "Jessica"

result = tuple(developer)

print(result)
# ('J', 'e', 's', 's', 'i', 'c', 'a')

因为字符串可以一个字符一个字符地进行迭代,所以:

"Jessica"
   ↓
J e s s i c a
   ↓
tuple()
   ↓
('J', 'e', 's', 's', 'i', 'c', 'a')

什么是可迭代对象(Iterable)?

  • 可迭代对象(Iterable):简单来说,就是可以逐个取出其中元素的对象。

常见的可迭代对象包括:

类型 示例
字符串 "Python"
列表 [1, 2, 3]
元组 (1, 2, 3)
集合 {1, 2, 3}
字典 {"name": "Alice"}
range range(5)

因此,tuple() 不仅可以接收字符串,也可以接收列表等对象。

例如:

languages = ["Python", "Java", "Rust"]

result = tuple(languages)

print(result)
# ('Python', 'Java', 'Rust')

原来的列表:

["Python", "Java", "Rust"]

转换后:

("Python", "Java", "Rust")

使用 in 判断元素是否存在

可以使用:

in

判断一个元素是否存在于元组中。

例如:

programming_languages = ("Python", "Java", "C++", "Rust")

print("Rust" in programming_languages)
# True

print("JavaScript" in programming_languages)
# False

in 的结果是一个布尔值:

True

或者:

False

也就是说:

"Rust" in programming_languages

可以理解为:

“Rust 在这个元组里面吗?”

答案是:

True

使用 not in 判断元素不存在

除了 in,还可以使用:

not in

判断某个元素是否不存在于元组中。

例如:

languages = ("Python", "Java", "Rust")

print("Python" not in languages)
# False

print("JavaScript" not in languages)
# True

两者可以这样理解:

表达式 含义
x in data x 是否存在于 data
x not in data x 是否不存在于 data

元组解包(Tuple Unpacking)

元组支持一个非常实用的操作:

解包(Unpacking)

例如:

developer = ("Alice", 34, "Rust Developer")

可以把元组中的三个元素分别赋值给三个变量:

name, age, job = developer

print(name)
# Alice

print(age)
# 34

print(job)
# Rust Developer

执行过程可以理解成:

developer
    ↓
("Alice", 34, "Rust Developer")
    ↓
    ↓
    ↓
name    age    job
 ↓       ↓      ↓
Alice    34    Rust Developer

因此:

name

得到:

Alice
age

得到:

34
job

得到:

Rust Developer

解包时需要注意数量

普通解包时,变量的数量需要和元组中的元素数量对应。

例如:

numbers = (10, 20, 30)

a, b, c = numbers

print(a)
# 10

print(b)
# 20

print(c)
# 30

这是正确的,因为:

3 个元素
   ↓
3 个变量

如果变量数量不匹配:

numbers = (10, 20, 30)

a, b = numbers

就会产生错误。

因为:

3 个元素
   ↓
只有 2 个变量

同样:

a, b, c, d = numbers

也会产生错误。


使用 * 接收剩余元素

Python 可以使用星号:

*

在解包时接收剩余元素。

例如:

developer = ("Alice", 34, "Rust Developer")

name, *rest = developer

print(name)
# Alice

print(rest)
# [34, 'Rust Developer']

这里:

name

接收第一个元素:

Alice

而:

*rest

负责接收剩余元素:

34
Rust Developer

需要特别注意:

*rest 收集到的结果是一个列表,而不是元组。

因此:

print(type(rest))
# <class 'list'>

* 可以接收中间的元素

* 不一定只能放在最后。

例如:

numbers = (1, 2, 3, 4, 5)

first, *middle, last = numbers

print(first)
# 1

print(middle)
# [2, 3, 4]

print(last)
# 5

这里:

first
 ↓
1

middle
 ↓
[2, 3, 4]

last
 ↓
5

因此:

  • first 获取第一个元素。
  • last 获取最后一个元素。
  • middle 使用 * 获取中间剩余的元素。

元组切片(Slicing)

和列表一样,元组也支持:

切片(Slicing)

切片可以从元组中提取一部分元素。

基本语法:

tuple[start:stop]

例如:

desserts = ("cake", "pie", "cookies", "ice cream")

result = desserts[1:3]

print(result)
# ('pie', 'cookies')

对应关系:

索引:   0       1          2          3
         ↓       ↓          ↓          ↓
元素:  cake     pie      cookies    ice cream

执行:

desserts[1:3]

表示:

从索引 1 开始
到索引 3 结束
但不包含索引 3

所以得到:

pie
cookies

最终结果是:

("pie", "cookies")

切片的结束索引不包含

Python 切片有一个非常重要的规则:

开始索引包含,结束索引不包含。

也就是:

[start:stop]
   ↑    ↑
 包含  不包含

例如:

numbers = (0, 1, 2, 3, 4)

print(numbers[1:4])
# (1, 2, 3)

这里包含:

索引 1
索引 2
索引 3

但是不包含:

索引 4

切片不会修改原元组

元组切片只是提取数据,不会修改原来的元组。

例如:

numbers = (1, 2, 3, 4, 5)

result = numbers[1:4]

print(numbers)
# (1, 2, 3, 4, 5)

print(result)
# (2, 3, 4)

原来的 numbers 没有发生变化。

而且切片得到的结果仍然是一个元组:

print(type(result))
# <class 'tuple'>

元组不能删除单个元素

由于元组是不可变的,所以不能删除其中某个元素。

例如:

developer = ("Jane Doe", 23, "Python Developer")

del developer[1]

会产生:

TypeError

因为:

Tuple
 ↓
不可变
 ↓
不能删除单个元素

同样,也不能使用列表中的:

append()
remove()
pop()

等方法来修改元组。


但可以删除整个元组

虽然不能删除元组中的单个元素,但是可以使用 del 删除整个元组变量:

developer = ("Alice", 34, "Rust Developer")

del developer

此时:

print(developer)

会产生:

NameError

因为变量 developer 已经被删除。

所以需要区分:

操作 是否允许
修改元组中的元素
删除元组中的一个元素
添加元素
删除整个元组变量

什么时候应该使用 Tuple?

那么问题来了:

什么时候使用元组,而不是列表?

可以先问自己一个问题:

这组数据以后需要修改吗?

如果答案是:

需要修改

例如购物车:

shopping_cart = ["Apple", "Banana"]

后面可能还要:

shopping_cart.append("Orange")

那么应该使用:

List

因为列表是可变的。


如果答案是:

不需要修改

例如一组固定的数据:

coordinate = (100, 200)

那么可以使用:

Tuple

因为元组可以表达:

这组数据创建后就不应该被修改。


List 和 Tuple 的选择

可以简单记住:

数据经常变化?
      │
      ├── 是 → List
      │
      └── 否 → Tuple

例如:

数据 推荐类型 原因
购物车商品 list 商品会增加、删除
待办事项 list 内容经常变化
学生列表 list 学生可能增加或删除
固定坐标 tuple 数据结构相对固定
固定配置 tuple 不希望被修改
固定的多个相关数据 tuple 数据通常不会变化

当然,这并不是绝对规则。

实际开发中选择 list 还是 tuple,还需要根据程序的具体需求决定。


元组的常见操作速查

操作 示例 作用
创建 ("A", "B") 创建元组
tuple() tuple([1, 2]) 转换为元组
正索引 data[0] 获取元素
负索引 data[-1] 从末尾获取元素
切片 data[1:3] 获取部分元素
in "A" in data 判断元素是否存在
not in "A" not in data 判断元素是否不存在
解包 a, b = data 分别获取元素
* 解包 a, *rest = data 接收剩余元素
del data 删除整个元组变量 删除变量
data[0] = x 修改元素 ❌ 不允许
del data[0] 删除元素 ❌ 不允许

元组核心知识总结

元组可以理解成:

一个有顺序、可以读取,但不能直接修改的容器。

例如:

developer = ("Alice", 34, "Rust Developer")

我们可以读取:

print(developer[0])
# Alice

可以切片:

print(developer[1:])
# (34, 'Rust Developer')

可以解包:

name, age, job = developer

print(name)
# Alice

也可以判断元素是否存在:

print("Alice" in developer)
# True

但是不能:

developer[0] = "Bob"

也不能:

del developer[0]

因为:

Tuple
  ↓
Immutable(不可变)

总结

  • 元组(Tuple) 是 Python 中一种有序的数据类型,可以保存多个元素。
  • 元组中的元素可以是不同的数据类型。
  • 元组通常使用 () 创建,也可以使用 tuple() 将可迭代对象转换成元组。
  • 元组支持正索引和负索引,可以通过索引访问元素。
  • 访问不存在的索引会产生 IndexError
  • 元组支持切片,而且切片的结束索引不包含在结果中。
  • innot in 可以用来判断元素是否存在。
  • 元组支持解包,可以把元素分别赋值给多个变量。
  • 使用 * 可以在解包时接收剩余元素,并且接收到的结果是一个列表。
  • 元组最大的特点是不可变(Immutable),因此不能直接修改、添加或删除其中的元素。
  • 如果数据需要频繁增加、删除或修改,通常使用 list;如果数据相对固定,可以考虑使用 tuple

记住元组最核心的一句话:

Tuple 是有序的,但创建之后不能直接修改其中的元素。