前言:
今天各位老铁们对“python元组数据类型”大致比较看重,兄弟们都需要剖析一些“python元组数据类型”的相关资讯。那么小编也在网摘上收集了一些对于“python元组数据类型””的相关资讯,希望咱们能喜欢,你们快快来学习一下吧!Hi,大家好。我是茶桁。
之前两节分别介绍了字符串和列表,今天,我们来讲讲另外一个常用到的数据类型:元组。
元组和列表很像,两者都是一组有序的数据的组合。但是也有很多不同点,比如元组内的元素一旦定义了就不可以再修改,因此元组称为不可变数据类型。
元组定义
元组的定义方式包括以下要点:
定义元组变量 = (), 或者变量 = tuple()可以使用变量 = (*iterable)定义含有数据的元组⚠️ 需要注意:如果元组中只有一个元素时,这唯一的元素后面也必须加逗号,这是为了区分其他元素标识这是一个元组: (1,)特例: 变量 = 1,2,3, 这种方式也可以定义为一个元组。元组的相关操作
由于元组是一个不可变的数据类型,因此其在创建之后只能使用索引进行访问,无法进行其他操作。访问方式其实和列表一样,同样可以使用切片方式获取元素。
元组可以进行切片操作,在访问数据这件事情上和列表几乎一样,没有什么区别,所以完全可以借鉴上一节我讲的内容来看,这里就不详细介绍了,仅仅给大家写出一些案例:
# 常见的元组切片索引查询操作tup = 1, 2, 3, 4, 5, 5, 4, 3, 2, 1print('[:]:\t',tup[:]) # 获取全部print('[::]:\t', tup[::]) # 获取全部print('[1:]:\t', tup[1:]) # 从索引1开始获取到最后print('[1:3]:\t', tup[1:3]) # 从索引1开始索引到3之前print('[:3]:\t', tup[:3]) # 从0开始索引到3之前print('[1:5:2]:\t', tup[1:5:2]) # 从1开始索引到5之前,步进值为2print('[::2]:\t', tup[::2]) #从0开始索引到最后,步进值为2print('[5:1:-1]:\t', tup[5:1:-1]) # 从5开始往前索引到1, 步进值为-1。---[:]: (1, 2, 3, 4, 5, 5, 4, 3, 2, 1)[::]: (1, 2, 3, 4, 5, 5, 4, 3, 2, 1)[1:]: (2, 3, 4, 5, 5, 4, 3, 2, 1)[1:3]: (2, 3)[:3]: (1, 2, 3)[1:5:2]: (2, 4)[::2]: (1, 3, 5, 4, 2)[5:1:-1]: (5, 5, 4, 3)
除了常用的切片操作之外,和列表一样,元组也能使用一些基本函数来完成查询操作
# 获取元组的长度print(len(tup))# 统计一个元素在元组中出现的次数print(tup.count(5))# 获取一个元素在元组内的下标(索引值)print(tup.index(5, 1, 9))---1024
除此之外,元组还可以引用基础的数学运算符+和*来进行加和乘的运算。
# 加和乘操作print((1, 2, 3) + ('a', 'b'))print((1, 2, 3) * 5)---(1, 2, 3, 'a', 'b')(1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3)
有一个同学曾经问过我:既然元组是不可修改的,那为什么还能用加和乘的运算呢?
不知道在座的小伙伴有没有这种想法?
这样吧,我重新写一段代码,小伙伴们应该就明白了:
# 元组是这样的tup = (1, 2, 3, 4)print(id(tup))tup2 = (3, 4, 5, 6)tup = tup + tup2print(id(tup))---4446580864445062912044506479844459297216
不知道大家看明白没有。解释一下,其实就是说,在进行加法和乘法运算的时候,即便我们的变量名是一样的,实际上也是生成了一个新的元组,而不是之前那一个了。所以这个并非是修改和更新,而是创建。
为了对比,我再写一段更新的代码给大家看:
# 尝试更新元组tup = (1, 2, 3, 4, 5, 6)print(tup[2])del tup[2]print(tup)---TypeError: 'tuple' object doesn't support item deletion
可以看到,报错提示了,tuple对象不支持删除项目
作为对比,我们看看列表的:
# 看看列表更新(只看id)items = [1, 2, 3, 4, 5, 6]print(id(items))del items[2]print(id(items), '\t',items)---44504159364450415936 [1, 2, 4, 5, 6]
可以看到,不仅是内部元素被删除了,并且id完全没有变化。也就是说,我们是在这个列表本身做了删除动作,并未生成新的列表。关于这部分,我们上一节中的深拷贝和浅拷贝讲的很清楚,大家可以回去好好看看理解一下。
元组推导式 生成器
在起初,我们先来看看元组是否和列表一样支持使用推导式。
tup = (i for i in range(10))print(tup)---<generator object <genexpr> at 0x109cfa570>
这段并非是报错,而是打印出了tup的类型:生成器对象。
我们之前学过,使用列表推导式生成的结果是一个列表,但是元组似乎和列表并不一样,生成的结果是一个生成器对象。
列表推导式 ==> [变量运算 for i in 容器] ==> 结果 是一个 列表元组推导式 ==> (变量运算 for i in 容器) ==> 结果 是一个 生成器
那这里就有个疑问了,什么是生成器?
生成器是一个特殊的迭代器,生成器可以自定义,也可以使用元组推导式去定义。
生成器是按照某种算法去推算下一个数据或结果,只需要往内存中存储一个生成器,节约内存消耗,提升性能。
语法
里面是推导式,外面是一个()的结果就是一个生成器自定义生成器,含有yield关键字的函数就是生成器
那么,我们到底应该怎样操作生成器呢?
既然生成器是迭代器的一种,那我们是否可以使用迭代器的操作方法来操作生成器呢?
说干就干,让我们直接操作做实验:
tup = (i for i in range(10))print(next(tup))print(next(tup))print(next(tup))print(list(tup))---012[3, 4, 5, 6, 7, 8, 9]
没毛病,确实支持next()函数,并且内部元素在使用后也被移除了。
# 让我们将其转为元组print(tuple(tup))---()
哎,为什么里面是空的?那是因为,我们上一段代码中的最后一句,已经讲所有迭代器内的元素转为了列表,素衣目前迭代器tup内是没有任何元素了,所以我们转过来必须是空的。
再来生成一个,我们来试试用for对它进行循环:
tup = (i for i in range(10))for i in tup: print(i, end=" ") ---0 1 2 3 4 5 6 7 8 9
可以看到结果没有问题。可以推断出,生成器和迭代器没有任何区别,我们在平时使用的时候,就直接将它作为迭代器使用就可以了。
yield关键字
在之前,我们提到了,含有yield关键字的函数就是生成器。
它返回的结果是一个迭代器。我们可以理解为,生成器函数就是一个返回迭代器的函数。
那么yield有哪些需要注意的点呢?我们先在下面列一下,之后再带着大家一起过:
yield和函数中的return有点像
共同点: 执行到这个关键字后会把结果返回来不同点:return会把结果返回,并结束当前函数的调用yield会返回结果,并记住当前代码执行的位置,下一次调用时会从上一次离开的位置继续向下执行。
上实验:
# 定义一个普通函数def func(): print('Hello yield') return 'yield' print('Hello again')func()func()---Hello yieldHello yield
在这个自定义函数内,return执行的时候,就会结束当前函数的调用,而在之前,第一个print()函数正确执行了,但是第二个print()函数因为在return之后,所以并未运行。即便我们一共执行了两次函数,可是也仅仅是讲第一个print()函数执行了两次。
# 尝试使用yield定义一个生成器函数def func(): print('Hello yield') yield 'yield' print('Hello again') yield 'again'# 调用生成器函数, 返回一个迭代器res = func()next(res)next(res)---Hello yieldHello again
可以看到,当我们使用next()函数的时候,迭代器起作用了,每执行一次,分别调用第一个yield之前和之后的print(),也就是说继续执行了。
那如何验证yield的返回呢?我们将这段代码改造一下:
# 尝试使用yield定义一个生成器函数def func(): print('Hello yield') yield 'return yield' print('Hello again') yield 'return again'# 调用生成器函数, 返回一个迭代器res = func()str = next(res)print(str)str = next(res)print(str)---Hello yieldreturn yieldHello againreturn again
没问题,依次打印出了返回值return yield和return again。
还记得我们之前教过,使用list函数去调用,可以讲迭代器的返回结果,作为容器的元素,让我们再来改造一下这段代码:
# 尝试使用yield定义一个生成器函数def func(): print('Hello yield') yield 'return yield' print('Hello again') yield 'return again'# 调用生成器函数, 返回一个迭代器res = func()items = list(res)print(items)---Hello yieldHello again['return yield', 'return again']
我们看见,确实,返回结果被依次放入了一个list容器中。
当然,除了list函数之外,还可以使用for来获取迭代器内容:
# 尝试使用yield定义一个生成器函数def func(): print('Hello yield') yield 'return yield' print('Hello again') yield 'return again'# 调用生成器函数, 返回一个迭代器res = func()items = []for i in res: items.append(i)print(items)---Hello yieldHello again['return yield', 'return again']
我们来分析一下在以上这几段代码中,生成器函数调用时到底是什么过程。
首先,调用生成器函数,返回一个迭代器:
第一次去调用迭代器,走到当前的生成器函数中,遇到第一个yield, 把return yield返回,并且记住当前的执行状态(位置),暂停了执行,等待下一次的调用第二次去调用迭代器,从上一次遇到的yield位置开始执行,遇到了第二个yield,把return again返回,并重新记录状态,暂停执行,等待下一次调用。如果最后又调用了迭代器,那么会从上一次的yield位置开始,可是后面没有了,就会超出范围,抛出异常:StopIteration:。
那么这种一次一次调用执行的方式什么时候适用呢?比如说,我们在处理一个非常大的数据,电脑可能吃不住,这个时候我们就可以拆开来一次一次的执行获取结果。
小练习
为了能达到练习的目的,从这一节开始,所有练习可以不在课程中展示了。大家先做一下,然后可以在我下一节课中的源码中去找答案,然后来看看和自己做的是否一样。以下所有练习必须使用列表推导式来实现有些练习不止一个方法,大家尝试用多种方法来实现一下。做完的小伙伴可以在课程后面留言讨论。上一节的练习已经放到本次教程的源码内,可以在此获取:
今天就一个练习:使用生成器改写斐波那契数列函数
标签: #python元组数据类型