01. 元组的基本概念
1.1 元素的介绍
- 元组(Tuple)是一个不可变的有序序列,可以看成是一个不可变的列表(与列表类似,但元素一旦确定就不可变)。
- 因此元组在定义时便要确定元组内的元素。
- 因为其不可变性,所以相对于列表而言数据更加安全。
- 在操作上,元组没有增删改的操作,其他操作都与列表类似,如:
- 索引和切片(但只能用于获取元素)。
- 运算符:+、*、in、not in、比较运算符。
- 遍历操作。
元组中只有一个元素就是一元组,如:
(1, )
;有两个元素就称为二元组,如:(1, 2)
;n个元素就是n元组,如:(1, 2, 3, ……, n)
1.2 元组的定义
1.2.1 字面量()定义法
元组由一个小括号包裹,元素用逗号隔开。(在声明时可以不写小括号) ```python tup0 = () # 空元组 print(tup0, type(tup0)) # ()
tup1 = (1, 2, 3, 4, 5)
print(tup1, type(tup1)) # (1, 2, 3, 4, 5)
tup2 = 1, 2, 3, 4, 5
print(tup2, type(tup2)) # (1, 2, 3, 4, 5)
- 如果在声明元组时只有一个元素,需要在元素后面加上逗号,告诉解释器这不是运算符中的括号。
```python
t1 = (1, )
print(t1, type(t1)) # (1,) <class 'tuple'>
t2 = (1)
print(t2, type(t2)) # 1 <class 'int'>
t3 = 1,
print(t3, type(t3)) # (1,) <class 'tuple'>
1.2.2 tuple()函数构建元组
- 使用
tup = tuple(seq)
函数将序列seq构造成元组。 ```python tup1 = tuple(“Hello”) # 通过字符串构造 print(tup1) # (‘H’, ‘e’, ‘l’, ‘l’, ‘o’)
tup2 = tuple([12, 31, 53, 43, 51]) # 通过列表构造 print(tup2) # (12, 31, 53, 43, 51)
<a name="RJkNZ"></a>
### 1.3 元组的不可变性
- 元组存在不可变性,因此元组并没有添加、删除、修改等方法。
```python
# TypeError: 'tuple' object does not support item assignment
# a = (1, 2, 3)
# a[1] = 3
- 但是,元组的不可变是相对的,如果元组中的某个元素是可变的,那么,在不删除这个元素的情况下,可以对这个元素进行修改。 ```python a = (1, [1, 0]) print(a) # (1, [1, 0])
a[1][1] = 1 print(a) # (1, [1, 1])
<a name="TtbMM"></a>
### 1.4 元组适用的场景
- 元组和列表是十分相似的,唯一的区别在于元组的元素是不可变的。
- 结合这一特性,当数据个数确定,且不元素不发生变化的时候建议使用元素。
- 虽然这种情况也可以用列表存储,但是这种情况更符合元素的特性。
- 而且在存储相同内容时,元组比列表更加节省内存。
- 如一年中有31天的月份可以存储为`month = [1, 3, 5, 7, 8, 10, 12]`,也可存储为`month = (1, 3, 5, 7, 8, 10, 12)`,但更推荐存储为`month = (1, 3, 5, 7, 8, 10, 12)`。
```python
import sys
month_lst = [1, 3, 5, 7, 8, 10, 12]
month_tup = (1, 3, 5, 7, 8, 10, 12)
# sys.getsizeof()用于计算元素在内存中所占的大写
print(sys.getsizeof(month_lst)) # 120
print(sys.getsizeof(month_tup)) # 96
-
02. 元组的相关操作
2.1 元组的索引和切片
元组和字符串、列表一样,是一个有序的序列,因此也可以通过索引获取元素。
tup = (1, 2, 3, 4, 5)
print(tup[2]) # 3,正向索引
print(tup[-2]) # 4,负向索引
切片也一样:
tup = (1, 2, 3, 4, 5)
print(tup[1:-2]) # (2, 3)
但由于元组是个不可变的序列,因此元组与字符串一样,只能通过索引和切片获取元组的一个或多个元素,但不能像列表那样通过索引和切片修改元素。
tup = (1, 2, 3, 4, 5)
tup[2] = 54 # 报错,TypeError: 'tuple' object does not support item assignment
2.2 元组的运算
2.2.1 +与*运算
元组也可以使用“+”和“*”进行拼接和重复。
t1 = (1, 2, 3)
t2 = (4, 5, 6)
print(t1 + t2) # (1, 2, 3, 4, 5, 6)
print(t2 * 3) # (4, 5, 6, 4, 5, 6, 4, 5, 6)
列表还有
+=
和*=
两个运算符,但应该这两个运算符改变的是序列本身的结构。由于元组其自身的不可变性,因此元组中没有
+=
和*=
这两个运算符。2.2.2 in与not in
元组也可以使用in和not in判断元组内是否拥有某个元素。
tup = (1, 2, 3, 4, 5)
print(3 in tup, 6 in tup) # True False
print(6 not in tup, 3 not in tup) # True False
2.2.3 比较运算符
元组的比较运算符也与列表一样,除了==和!=外,其他的都是从左往右依次比较,得到结果后立刻停止。
tup1 = (1, 1, 1)
tup2 = (1, 1, 2)
print(tup1 < tup2) # True
print(tup1 == tup2) # False
print(tup1 != tup2) # True
2.3 元组的遍历
元组的遍历与列表一样,也有三种方式:
由于元组的不可变性,因此与列表相比没有insert()、remove()等修改元素。
-
2.4.1 count()统计指定元素出现次数
tup.count(obj)
:用于统计给定元素obj在元组tup中出现的次数。tup = (5, 5, 1, 3, 2, 5)
print(tup.count(5)) # 3
2.4.2 index()查找指定元素的索引位置
tup.index(obj)
函数用于查找元素obj在元组tup中首次出现的位置,找不到会报错。index()
函数共有以下三种形式:tup.index(obj)
:在整个tup中从左往右查找元素obj。tup = (5, 5, 1, 3, 2, 5)
print(tup.index(5)) # 0
tup.index(obj, start_index)
:从tup的start_index开始一直到最后这段范围中查找元素obj。tup = (5, 5, 1, 3, 2, 5)
print(tup.index(5, 4)) # 5
tup.index(obj, start_i, end_i)
:从tup的start_i开始到end_i结束(不包含end_i)这段范围中查找元素obj。tup = (5, 5, 1, 3, 2, 5)
print(tup.index(5, 1, 3)) # 1
在元组中找不到指定元素将报元素不在元组中的错误。
tup = (5, 5, 1, 3, 2, 5)
print(tup.index(7)) # ValueError: tuple.index(x): x not in tuple
2.5 元组的打包与解包
2.5.1 打包与解包的基本介绍
元组除了用于存储不变的数据外,最大的用处就是对数据继续打包和解包的操作。
在02. 列表 — 2.3.4 拆包(解包)中实际上已经简单介绍过解包了。
2.5.2 打包
所谓打包实际上就是定义一个元组。
打包准确来讲就是定义多个数据,数据间用逗号分割,然后将所有数据赋值给一个变量,在这个过程中,所有的数据就会被打包成一个元组。
a = 10, 23, 45, 6, 7, 89
print(a, type(a)) # (10, 23, 45, 6, 7, 89) <class 'tuple'>
2.5.3 解包
解包实际上就是将一个序列中的多个数据赋值给多个变量的过程,是打包的逆过程。
tup = (10, 23, 45, 6, 7, 89)
a, b, c, d, e, f = tup
print(a, b, c, d, e, f) # 10 23 45 6 7 89
打包成的一定是元组,但是任意的序列都可以解包,不一定非要是元组。 ```python
解包列表
a, b, c = [1, 2, 3] print(a, b, c) # 1 2 3
解包字符串
a, b, c = “123” print(a, b, c) # 1 2 3
解包range()序列
a, b, c = range(3) print(a, b, c) # 0 1 2
<a name="Xu5GX"></a>
#### 2.5.4 解包常见的错误(变量数与数据量不匹配)
- 解包的过程中变量的个数与序列中元素的个数不一致时,就会报错。
- 当变量的个数少于序列中元素的个数时:
- 报错内容:ValueError: too many values to unpack (expected n)
- 解读:要解压缩的值太多(应为n个),即当有n个变量,序列中有m个元素,且m > n时,应该要被解压并赋值的数据应该只有n个,但实际解包了m个数据,多出来的数据没有变量可以赋值,故报错。
```python
tup = (10, 23, 45, 6, 7, 89)
a, b = tup # ValueError: too many values to unpack (expected 2)
当变量的个数多于序列中元素的个数时:
- 报错内容:ValueError: not enough values to unpack (expected m, got n)
- 解读:没有足够的值来解包(预期为m,实际为n),即当有m个变量,序列中有n个元素,且m > n时,m个变量共同期待着序列能够解包出m个元素,但序列中只有n个元素,故此次解包无法给所有的变量赋值,因此报错。
tup = (10, 23, 45, 6, 7, 89)
a, b, c, d, e, f, g, h, i = tup # ValueError: not enough values to unpack (expected 9, got 6)
变量的个数少于序列中元素的个数的情况是可以被解决的,但变量的个数多于序列中元素的个数的情况无法被解决。
- 解决方案:使用星号表达式,让一个变量接收多个值。
- 以如下程序为例,元组前两个元素10和23会对应赋值给变量m和变量n。
剩下还有元素
45, 6, 7, 89
,当碰到带星号*
的变量时,Python会将这部分数据封装成一个列表,并将这个列表赋值给带星号的变量。tup = (10, 23, 45, 6, 7, 89)
m, n, *other = tup
print(m, n) # 10 23
print(other, type(other)) # [45, 6, 7, 89] <class 'list'>
带星号的变量在变量队列中只有一个,但其在队列中位置的变化会影响到变量的赋值。
当带星号的变量在变量队列最前面时:
- 即一个带星号的变量,后面n个普通变量。
- Python会先将序列中后n个数据对应赋值给普通变量,然后将前面剩下的数据封装成列表赋值给带星变量。
tup = (10, 23, 45, 6, 7, 89)
*begin_other, m, n = tup
print(begin_other, m, n) # [10, 23, 45, 6] 7 89
当带星号的变量在变量队列中间时:
- 即左边m个普通变量,中间一个带星号的变量,右边n个普通变量。
- Python会先将序列中前m个和后n个数据对应赋值给普通变量,然后将中间剩下的数据封装成列表赋值给带星变量。
tup = (10, 23, 45, 6, 7, 89)
m, *center_other, n = tup
print(m, center_other, n) # 10 [23, 45, 6, 7] 89
当带星号的变量在变量队列中间时:
- 即左边m个普通变量,右边一个带星号的变量。
- Python会先将序列中前m个数据对应赋值给普通变量,然后将最后剩下的数据封装成列表赋值给带星变量。
tup = (10, 23, 45, 6, 7, 89)
m, n, *end_other = tup
print(m, n, end_other) # 10 23 [45, 6, 7, 89]
注意,带星变量最多只能有一个,否则会报错:
SyntaxError: multiple starred expressions in assignment
,即赋值中的多星号表达式。tup = (10, 23, 45, 6, 7, 89)
a, b, *other1, *other2, c, d = tup # SyntaxError: multiple starred expressions in assignment