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)

  1. - 如果在声明元组时只有一个元素,需要在元素后面加上逗号,告诉解释器这不是运算符中的括号。
  2. ```python
  3. t1 = (1, )
  4. print(t1, type(t1)) # (1,) <class 'tuple'>
  5. t2 = (1)
  6. print(t2, type(t2)) # 1 <class 'int'>
  7. t3 = 1,
  8. 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)

  1. <a name="RJkNZ"></a>
  2. ### 1.3 元组的不可变性
  3. - 元组存在不可变性,因此元组并没有添加、删除、修改等方法。
  4. ```python
  5. # TypeError: 'tuple' object does not support item assignment
  6. # a = (1, 2, 3)
  7. # a[1] = 3
  • 但是,元组的不可变是相对的,如果元组中的某个元素是可变的,那么,在不删除这个元素的情况下,可以对这个元素进行修改。 ```python a = (1, [1, 0]) print(a) # (1, [1, 0])

a[1][1] = 1 print(a) # (1, [1, 1])

  1. <a name="TtbMM"></a>
  2. ### 1.4 元组适用的场景
  3. - 元组和列表是十分相似的,唯一的区别在于元组的元素是不可变的。
  4. - 结合这一特性,当数据个数确定,且不元素不发生变化的时候建议使用元素。
  5. - 虽然这种情况也可以用列表存储,但是这种情况更符合元素的特性。
  6. - 而且在存储相同内容时,元组比列表更加节省内存。
  7. - 如一年中有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)`。
  8. ```python
  9. import sys
  10. month_lst = [1, 3, 5, 7, 8, 10, 12]
  11. month_tup = (1, 3, 5, 7, 8, 10, 12)
  12. # sys.getsizeof()用于计算元素在内存中所占的大写
  13. print(sys.getsizeof(month_lst)) # 120
  14. print(sys.getsizeof(month_tup)) # 96
  • 除了这种场景外,建议使用列表,因为操作数据相对灵活。

    02. 元组的相关操作

    2.1 元组的索引和切片

  • 元组和字符串、列表一样,是一个有序的序列,因此也可以通过索引获取元素。

    1. tup = (1, 2, 3, 4, 5)
    2. print(tup[2]) # 3,正向索引
    3. print(tup[-2]) # 4,负向索引
  • 切片也一样:

    1. tup = (1, 2, 3, 4, 5)
    2. print(tup[1:-2]) # (2, 3)
  • 但由于元组是个不可变的序列,因此元组与字符串一样,只能通过索引和切片获取元组的一个或多个元素,但不能像列表那样通过索引和切片修改元素。

    1. tup = (1, 2, 3, 4, 5)
    2. tup[2] = 54 # 报错,TypeError: 'tuple' object does not support item assignment

    2.2 元组的运算

    2.2.1 +与*运算

  • 元组也可以使用“+”和“*”进行拼接和重复。

    1. t1 = (1, 2, 3)
    2. t2 = (4, 5, 6)
    3. print(t1 + t2) # (1, 2, 3, 4, 5, 6)
    4. print(t2 * 3) # (4, 5, 6, 4, 5, 6, 4, 5, 6)
  • 列表还有+=*=两个运算符,但应该这两个运算符改变的是序列本身的结构。

  • 由于元组其自身的不可变性,因此元组中没有+=*=这两个运算符。

    2.2.2 in与not in

  • 元组也可以使用in和not in判断元组内是否拥有某个元素。

    1. tup = (1, 2, 3, 4, 5)
    2. print(3 in tup, 6 in tup) # True False
    3. print(6 not in tup, 3 not in tup) # True False

    2.2.3 比较运算符

  • 元组的比较运算符也与列表一样,除了==和!=外,其他的都是从左往右依次比较,得到结果后立刻停止。

    1. tup1 = (1, 1, 1)
    2. tup2 = (1, 1, 2)
    3. print(tup1 < tup2) # True
    4. print(tup1 == tup2) # False
    5. print(tup1 != tup2) # True

    2.3 元组的遍历

  • 元组的遍历与列表一样,也有三种方式:

    • 通过索引遍历:

      1. tup = (1, 3, 5, 7, 8, 10, 12)
      2. for i in range(len(tup)):
      3. print(tup[i])
    • 直接遍历元素:

      1. tup = (1, 3, 5, 7, 8, 10, 12)
      2. for ele in tup:
      3. print(ele)
    • enumerate枚举:

      1. tup = (1, 3, 5, 7, 8, 10, 12)
      2. for index, ele in enumerate(tup):
      3. print(index, ele)

      2.4 元组的相关函数

  • 由于元组的不可变性,因此与列表相比没有insert()、remove()等修改元素。

  • 元组对象就只有count()index()两个函数。

    2.4.1 count()统计指定元素出现次数

  • tup.count(obj):用于统计给定元素obj在元组tup中出现的次数。

    1. tup = (5, 5, 1, 3, 2, 5)
    2. print(tup.count(5)) # 3

    2.4.2 index()查找指定元素的索引位置

  • tup.index(obj)函数用于查找元素obj在元组tup中首次出现的位置,找不到会报错。

  • index()函数共有以下三种形式:

    • tup.index(obj):在整个tup中从左往右查找元素obj。

      1. tup = (5, 5, 1, 3, 2, 5)
      2. print(tup.index(5)) # 0
    • tup.index(obj, start_index):从tup的start_index开始一直到最后这段范围中查找元素obj。

      1. tup = (5, 5, 1, 3, 2, 5)
      2. print(tup.index(5, 4)) # 5
    • tup.index(obj, start_i, end_i):从tup的start_i开始到end_i结束(不包含end_i)这段范围中查找元素obj。

      1. tup = (5, 5, 1, 3, 2, 5)
      2. print(tup.index(5, 1, 3)) # 1
  • 在元组中找不到指定元素将报元素不在元组中的错误。

    1. tup = (5, 5, 1, 3, 2, 5)
    2. print(tup.index(7)) # ValueError: tuple.index(x): x not in tuple

    2.5 元组的打包与解包

    2.5.1 打包与解包的基本介绍

  • 元组除了用于存储不变的数据外,最大的用处就是对数据继续打包和解包的操作。

  • 在02. 列表 — 2.3.4 拆包(解包)中实际上已经简单介绍过解包了。

    2.5.2 打包

  • 所谓打包实际上就是定义一个元组。

  • 打包准确来讲就是定义多个数据,数据间用逗号分割,然后将所有数据赋值给一个变量,在这个过程中,所有的数据就会被打包成一个元组。

    1. a = 10, 23, 45, 6, 7, 89
    2. print(a, type(a)) # (10, 23, 45, 6, 7, 89) <class 'tuple'>

    2.5.3 解包

  • 解包实际上就是将一个序列中的多个数据赋值给多个变量的过程,是打包的逆过程。

    1. tup = (10, 23, 45, 6, 7, 89)
    2. a, b, c, d, e, f = tup
    3. 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

  1. <a name="Xu5GX"></a>
  2. #### 2.5.4 解包常见的错误(变量数与数据量不匹配)
  3. - 解包的过程中变量的个数与序列中元素的个数不一致时,就会报错。
  4. - 当变量的个数少于序列中元素的个数时:
  5. - 报错内容:ValueError: too many values to unpack (expected n)
  6. - 解读:要解压缩的值太多(应为n个),即当有n个变量,序列中有m个元素,且m > n时,应该要被解压并赋值的数据应该只有n个,但实际解包了m个数据,多出来的数据没有变量可以赋值,故报错。
  7. ```python
  8. tup = (10, 23, 45, 6, 7, 89)
  9. 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个元素,故此次解包无法给所有的变量赋值,因此报错。
      1. tup = (10, 23, 45, 6, 7, 89)
      2. 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会将这部分数据封装成一个列表,并将这个列表赋值给带星号的变量。

      1. tup = (10, 23, 45, 6, 7, 89)
      2. m, n, *other = tup
      3. print(m, n) # 10 23
      4. print(other, type(other)) # [45, 6, 7, 89] <class 'list'>
    • 带星号的变量在变量队列中只有一个,但其在队列中位置的变化会影响到变量的赋值。

      • 当带星号的变量在变量队列最前面时:

        • 即一个带星号的变量,后面n个普通变量。
        • Python会先将序列中后n个数据对应赋值给普通变量,然后将前面剩下的数据封装成列表赋值给带星变量。
          1. tup = (10, 23, 45, 6, 7, 89)
          2. *begin_other, m, n = tup
          3. print(begin_other, m, n) # [10, 23, 45, 6] 7 89
      • 当带星号的变量在变量队列中间时:

        • 即左边m个普通变量,中间一个带星号的变量,右边n个普通变量。
        • Python会先将序列中前m个和后n个数据对应赋值给普通变量,然后将中间剩下的数据封装成列表赋值给带星变量。
          1. tup = (10, 23, 45, 6, 7, 89)
          2. m, *center_other, n = tup
          3. print(m, center_other, n) # 10 [23, 45, 6, 7] 89
      • 当带星号的变量在变量队列中间时:

        • 即左边m个普通变量,右边一个带星号的变量。
        • Python会先将序列中前m个数据对应赋值给普通变量,然后将最后剩下的数据封装成列表赋值给带星变量。
          1. tup = (10, 23, 45, 6, 7, 89)
          2. m, n, *end_other = tup
          3. print(m, n, end_other) # 10 23 [45, 6, 7, 89]
    • 注意,带星变量最多只能有一个,否则会报错:SyntaxError: multiple starred expressions in assignment,即赋值中的多星号表达式。

      1. tup = (10, 23, 45, 6, 7, 89)
      2. a, b, *other1, *other2, c, d = tup # SyntaxError: multiple starred expressions in assignment