tongsiying

阅读|运动|自律

0%

第9篇:高级数据类型之元组

基础数据类型:
  • int,整数类型(整形)
  • float,浮点类型(浮点型)
  • complex,复数类型
  • bool,布尔类型
  • str,字符串类型

高级数据类型

  • list,列表类型

  • tuple,元组类型

  • dict,字典类型

  • set,集合类型

学习目标:

掌握元组的各种操作(知识点应用案例)

本节问题

  • 问题1:元组的定义是什么?和列表相比有什么不同?
  • 问题2:元组如何创建,有什么注意点?
  • 问题3:元组有哪些方法和运算?对元素进行增删改查有哪些相关的方法?
  • 问题4:元组的打包和解包如何操作?
  • 问题4:元组的元素是否可以改变?有什么注意点?

之前我们学习了Python中的列表,它是一种容器型数据类型,我们可以通过定义列表类型的变量来保存和操作多个元素。当然,Python中容器型的数据类型肯定不止列表一种,接下来我们为大家讲解另一种重要的容器型数据类型,它的名字叫元组(tuple)。

一、元组的定义

我们通过对比列表,看一下元组的定义:

  • 列表(list),是一个有序可变的容器,在里面可以存放多个不同类型数据元素可重复的元素。

  • 元组(tuple),是一个有序不可变的容器,在里面可以存放多个不同类型数据元素可重复的元素。

如何体现不可变呢?
记住一句话:《”我儿子永远不能换成是别人,但我儿子可以长大”》

  • 元组的特点:
    1. ()包裹,,为分隔符
    2. 内部元素有序
    3. 内部元素可变
    4. 内部元素类型可不同
    5. 内部元素可重复

二、元组的创建

在Python中,元组也是多个元素按照一定的顺序构成的序列。元组和列表的不同之处在于,元组是不可变类型,这就意味着元组类型的变量一旦定义,其中的元素不能再添加或删除,而且元素的值也不能进行修改。定义元组通常使用()字面量语法,也建议大家使用这种方式来创建元组。元组类型支持的运算符跟列表是一样。下面的代码演示了元组的定义和运算。

1
2
3
4
5
6
7
8
9
10
11
# 定义一个空元祖, 没有实际意义
v1 = tuple()
v2 = ()
# 定义一个非空元组 不可改变
v1 = (1,)
v2 = (11,22,33)
v3 = ("李连杰","Michael")
v4 = (('小明', 18), ('小张', 19), ('小红', 20))
# 建议:在元组的最后多加一个逗号
# 查看变量的类型
print(type(v1)) # <class 'tuple'>

一个元组中如果有两个元素,我们就称之为二元组;一个元组中如果五个元素,我们就称之为五元组。需要提醒大家注意的是,()表示空元组,但是如果元组中只有一个元素,需要加上一个逗号,否则()就不是代表元组的字面量语法,而是改变运算优先级的圆括号,所以('hello', )(100, )才是一元组,而('hello')(100)只是字符串和整数。我们可以通过下面的代码来加以验证。

1
2
3
4
5
6
7
8
9
10
11
12
13
# 空元组
a = ()
print(type(a)) # <class 'tuple'>
# 不是元组
b = ('hello')
print(type(b)) # <class 'str'>
c = (100)
print(type(c)) # <class 'int'>
# 一元组
d = ('hello', )
print(type(d)) # <class 'tuple'>
e = (100, )
print(type(e)) # <class 'tuple'>

建议在元组的最后多加一个逗号,用于标识他是一个元组。

三、元组的方法

由于元组最大的特点就是不可变,即不可修改,因此它的功能相比列表就少的多,下面是元组仅有的几个内置功能。

  • count(value):判断元组中value的个数

  • index(value, start=0, stop=9223372036854775807):判断value在元组中的索引位置,可以指定查找范围start和stop

  • 示例

    1
    2
    3
    a = (1,2,3,4,5,2,3,6,7)
    print(a.count(2)) # 2
    print(a.index(2)) # 2

四、元组的运算

和字符串和列表一样,元组也支持列表也支持拼接、重复、成员运算、索引和切片以及比较运算,这里将列表常见的运算总结如下,请大家参考。

元组支持的运算

  • () + (), 相加,两个元组相加生成新元组

  • () * int,相乘,元组*整型 将元组中的元素再创建N份并生成一个新的元组

  • in/not in,成员运算,判断元素是否在元组中

  • len, 获取元组的长度

  • [index],索引

  • [start:end],切片

  • [start:end:step​],步长

  • 比较运算:比的是对应索引位置上的元素的大小

使用示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
t1 = ('乔丹', '科比', '詹姆斯')  # 定义一个三元组
t2 = ('姚明', '易建联') # 定义一个二元组
# 获取长度
print(len(t1), len(t2)) # 3, 2

# 元组拼接 + 加法
t3 = t1 + t2
print(t3) # ('乔丹', '科比', '詹姆斯', '姚明', '易建联')

# 元组复制 * 乘法
t4 = t2 * 2
print(t4) # ('姚明', '易建联', '姚明', '易建联')

# 成员运算
print('乔丹' in t3) # True
print('孙悦' in t3) # False

# 索引
print(t3[0], t3[-5]) # 乔丹 乔丹
print(t3[4], t3[-1]) # 易建联 易建联

# 切片
print(t3[:3]) # ('乔丹', '科比', '詹姆斯')
print(t3[1:3]) # ('科比', '詹姆斯')
print(t3[1:]) # ('科比', '詹姆斯', '姚明', '易建联')
print(t3[::2]) # ('乔丹', '詹姆斯', '易建联')

# 反转元组
print(t3[::-1]) # ('易建联', '姚明', '詹姆斯', '科比', '乔丹')

# 比较
t5 = (11, 22, 33)
t6 = (44, 55, 66)
print(t5 == t6) # False
print(t5 > t6) # False
print(t5 < t6) # True

五、元组的遍历

和列表一样,想取出元组中的元素,可以使用for循环,有以下两种方式

方法一:

1
2
3
4
items = ('Python', 'Java', 'Go', 'Kotlin')

for index in range(len(items)):
print(items[index])

方法二:

1
2
3
4
items = ('Python', 'Java', 'Go', 'Kotlin')

for item in items:
print(item)

六、元组类型转换

其他类型转换为元组,使用tuple(其他类型数据),具体示例不再介绍,参考列表类型转化。

1
tuple(其他类型数据)

七、元组嵌套

由于元组和列表都可以充当容器,他们内部可以放很多元素,并且也支持元素内的各种嵌套。下面有三个练习题,可以对比一下列表和元组的区别。

练习题1:

判断是否可以实现,如果可以请写代码实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
li = ["Michael", [11,22,(88,99,100,),33],  "James",  ("Kobe", "Thomas",),  "David"]
# 0 1 2 3 4

# 1.请将 "James" 修改成 "张亚飞"
li[2] = "张亚飞"
index = li.index("James")
li[index] = "张亚飞"

# 2.请将 ("Kobe", "Thomas",) 修改为 ['奥巴马','特朗普']
li[3] = ['奥巴马','特朗普']

# 3.请将 88 修改为 87
li[1][2][0] = 87 # (报错,)

# 4.请将 "David" 删除,然后再在列表第0个索引位置插入 "Paul"
# li.remove("David")
# del li[-1]
li.insert(0,"Paul")

练习题2:

记住一句话:《”我儿子永远不能换成是别人,但我儿子可以长大”》

1
2
3
4
5
6
7
8
9
10
11
12
13
data = ("123",666,[11,22,33], ("Michael","李连杰",[999,666,(5,6,7)]) )

# 1.将 “123” 替换成 9 报错

# 2.将 [11,22,33] 换成 "张亚飞" 报错

# 3.将 11 换成 99
data[2][0] = 99
print(data) # ("123",666,[99,22,33], ("Michael","李连杰",[999,666,(5,6,7)]) )

# 4.在列表 [11,22,33] 追加一个44
data[2].append(44)
print(data) # ("123",666,[11,22,33,44], ("Michael","李连杰",[999,666,(5,6,7)]) )

练习题3:

动态的创建用户并添加到用户列表中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# 创建用户 5个
# user_list = [] # 用户信息
user_list = [ ("Michael","132"),("admin","123"),("Rose","123") ]

while True:
user = input("请输入用户名:")
if user == "Q":
brek
pwd = input("请输入密码:")
item = (user,pwd,)
user_list.append(item)

# 实现:用户登录案例
print("登录程序")
username = input("请输入用户名:")
password = input("请输入密码:")

is_success = False

for item in user_list:
# item = ("Michael","132") ("admin","123") ("Rose","123")
if username == item[0] and password == item[1]:
is_success = True
break

if is_success:
print("登录成功")
else:
print("登录失败")

八、知识点扩展

1. 命名元组

namedtuple是一个函数,它用来创建一个自定义的tuple对象,并且规定了tuple元素的个数,并可以用属性而不是索引来引用tuple的某个元素。这样一来,我们用namedtuple可以很方便地定义一种数据类型,它具备tuple的不变性,又可以根据属性来引用,使用十分方便。

1
2
3
4
5
6
7
8
9
10
from collections import namedtuple

Point = namedtuple('Point', ['x', 'y'])
p = Point(1, 2)
print(p.x)
print(p.y)

l1 = [Point(i, j) for i in range(5) for j in range(5)]
for p in l1:
print(p.x, p.y)

九、元组的应用场景

讲到这里,相信大家一定迫切的想知道元组有哪些应用场景,我们给大家举几个例子。

例子1:打包和解包操作

当我们把多个用逗号分隔的值赋给一个变量时,多个值会打包成一个元组类型;当我们把一个元组赋值给多个变量时,元组会解包成多个值然后分别赋给对应的变量,如下面的代码所示。

1
2
3
4
5
6
# 打包
a = 1, 10, 100
print(type(a), a) # <class 'tuple'> (1, 10, 100)
# 解包
i, j, k = a
print(i, j, k) # 1 10 100

在解包时,如果解包出来的元素个数和变量个数不对应,会引发ValueError异常,错误信息为:too many values to unpack(解包的值太多)或not enough values to unpack(解包的值不足)。

1
2
3
a = 1, 10, 100, 1000
# i, j, k = a # ValueError: too many values to unpack (expected 3)
# i, j, k, l, m, n = a # ValueError: not enough values to unpack (expected 6, got 4)

有一种解决变量个数少于元素的个数方法,就是使用星号表达式,我们之前讲函数的可变参数时使用过星号表达式。有了星号表达式,我们就可以让一个变量接收多个值,代码如下所示。需要注意的是,用星号表达式修饰的变量会变成一个列表,列表中有0个或多个元素。还有在解包语法中,星号表达式只能出现一次。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
a = 1, 10, 100, 1000
i, j, *k = a
print(i, j, k) # 1 10 [100, 1000]
i, *j, k = a
print(i, j, k) # 1 [10, 100] 1000
*i, j, k = a
print(i, j, k) # [1, 10] 100 1000
*i, j = a
print(i, j) # [1, 10, 100] 1000
i, *j = a
print(i, j) # 1 [10, 100, 1000]
i, j, k, *l = a
print(i, j, k, l) # 1 10 100 [1000]
i, j, k, l, *m = a
print(i, j, k, l, m) # 1 10 100 1000 []

需要说明一点,解包语法对所有的序列都成立,这就意味着对字符串、列表以及我们之前讲到的range函数返回的范围序列都可以使用解包语法。大家可以尝试运行下面的代码,看看会出现怎样的结果。

1
2
3
4
5
6
a, b, *c = range(1, 10)
print(a, b, c)
a, b, c = [1, 10, 100]
print(a, b, c)
a, *b, c = 'hello'
print(a, b, c)

现在我们可以反过来思考一下函数的可变参数,可变参数其实就是将多个参数打包成了一个元组,可以通过下面的代码来证明这一点。

1
2
3
4
5
6
7
8
9
10
def add(*args):
print(type(args), args)
total = 0
for val in args:
total += val
return total


add(1, 10, 20) # <class 'tuple'> (1, 10, 20)
add(1, 2, 3, 4, 5) # <class 'tuple'> (1, 2, 3, 4, 5)

例子2:交换两个变量的值

交换两个变量的值是编程语言中的一个经典案例,在很多编程语言中,交换两个变量的值都需要借助一个中间变量才能做到,如果不用中间变量就需要使用比较晦涩的位运算来实现。在Python中,交换两个变量ab的值只需要使用如下所示的代码。

1
a, b = b, a

同理,如果要将三个变量abc的值互换,即b赋给ac赋给ba赋给c,也可以如法炮制。

1
a, b, c = b, c, a

需要说明的是,上面并没有用到打包和解包语法,Python的字节码指令中有ROT_TWOROT_THREE这样的指令可以实现这个操作,效率是非常高的。但是如果有多于三个变量的值要依次互换,这个时候没有直接可用的字节码指令,执行的原理就是我们上面讲解的打包和解包操作。

例子3:让函数返回多个值

有的时候一个函数执行完成后可能需要返回多个值,这个时候元组类型应该是比较方便的选择。例如,编写一个找出列表中最大值和最小的函数。

1
2
3
4
5
6
7
8
9
10
11
12
def find_max_and_min(items):
"""找出列表中最大和最小的元素
:param items: 列表
:return: 最大和最小元素构成的二元组
"""
max_one, min_one = items[0], items[0]
for item in items:
if item > max_one:
max_one = item
elif item < min_one:
min_one = item
return max_one, min_one

上面函数的return语句中有两个值,这两个值会组装成一个二元组然后返回。所以调用find_max_and_min函数会得到这个二元组,如果愿意也可以通过解包语法将二元组中的两个值分别赋给两个变量。

十、元组和列表的比较

这里还有一个非常值得探讨的问题,Python中已经有了列表类型,为什么还需要元组这样的类型呢?这个问题对于初学者来说似乎有点困难,不过没有关系,我们先抛出观点,大家可以一边学习一边慢慢体会。

  1. 元组是不可变类型,不可变类型更适合多线程环境,因为它降低了并发访问变量的同步化开销。关于这一点,我们会在后面讲解多线程的时候为大家详细论述。

  2. 元组是不可变类型,通常不可变类型在创建时间和占用空间上面都优于对应的可变类型。我们可以使用sys模块的getsizeof函数来检查保存相同元素的元组和列表各自占用了多少内存空间。我们也可以使用timeit模块的timeit函数来看看创建保存相同元素的元组和列表各自花费的时间,代码如下所示。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    import sys
    import timeit

    a = list(range(100000))
    b = tuple(range(100000))
    print(sys.getsizeof(a), sys.getsizeof(b)) # 900120 800056

    print(timeit.timeit('[1, 2, 3, 4, 5, 6, 7, 8, 9]'))
    print(timeit.timeit('(1, 2, 3, 4, 5, 6, 7, 8, 9)'))
  3. Python中的元组和列表是可以相互转换的,我们可以通过下面的代码来做到。

    1
    2
    3
    4
    5
    6
    # 将元组转换成列表
    info = (1, 2, 3)
    print(list(info)) # [1, 2, 3]
    # 将列表转换成元组
    fruits = ['apple', 'banana', 'orange']
    print(tuple(fruits)) # ('apple', 'banana', 'orange')

十一、简单的总结

列表和元组都是容器型的数据类型,即一个变量可以保存多个数据。

列表是可变数据类型元组是不可变数据类型,所以列表添加元素、删除元素、清空、排序等方法对于元组来说是不成立的。

但是列表和元组都可以进行拼接成员运算索引和切片这些操作,就如同之前讲到的字符串类型一样,因为字符串就是字符按一定顺序构成的序列,在这一点上三者并没有什么区别。

我们推荐大家使用列表的生成式语法来创建列表,它很好用,也是Python中非常有特色的语法。

赞赏一下吧~