第六章 列表:推导式与切片
6.1 (重要) 列表推导式
6.1.1 为何要用列表推导式?
任务一:生成列表元素
为创建一个元素为整数平方项的列表squares,可使用如下方式
1 | #声明空列表 |
代码解析:
在for循环中,通过对序列的临时变量x,进行x****2**运算来创建列表的新元素
然后将新元素**append()**到列表中,直到循环结束
上述列表squares创建方式包含至少3行以上的代码
下面介绍的列表推导式只需一行代码就能生成相同的列表
任务二:初识列表推导式
知识点:列表推导式的语法
语法:newlist = [expression for expr1 in sequence1 if condition1]
expression:生成列表新元素的表达式
for expr1 in sequence1:遍历序列sequence1。必须包含至少一个for语句
if condition1:可选,筛选满足条件的元素
列表推导式中的for语句末尾没有冒号
描述:根据序列sequence1,筛选满足条件的元素,并根据表达式expression,生成新列表
返回:一个新列表。不管原序列是什么类型,返回的是列表list类型
1 | squares = [x**2 for x in range(10)] |
程序解析(列表推导式规则的角度):
列表推导式,由一个有**[]**括号所包含的表达式组成,用于定义列表元素生成规则
遍历原序列:for x in range(10) 遍历range(10)序列,并赋值给临时变量x
定义新元素生成的表达式:根据表达式 (x2),对x实现运算操作来生成新列表元素
自动添加元素:将新元素添加到列表中
squares类型为list对象
任务三:列表推导式的等价形式
知识点:列表推导式和普通代码的逻辑等价
列表推导式:newlist = [expression for expr1 in sequence1 if condition1]
等价的普通代码:
1 | newlist = [] |
列表推导式为从序列中生成新元素来创建列表提供了一个优雅且简明的方法。代码简洁,可读性强,是Python程序开发时应用最多的技术之一
任务四:函数映射角度来理解列表推导式
知识点:函数映射角度来理解列表推导式:
定义域:序列sequence1
函数映射:特定的表达式规则**(expression)**
值域:新的列表newlist
1 | squares = [x**2 for x in range(10)] |
程序解析(函数映射的角度):
定义域:序列range(10)
函数映射:表达式x**2
值域:新列表squares
6.1.2 深入列表推导式用法
任务一:再识列表推导式
知识点:列表推导式(list comprehension)
语法:newlist = [expression for expr1 in sequence1 if condition1]
又称列表解析式,它对序列或可迭代对象进行遍历(for)、过滤(if)或计算(expression),来生成新列表
简单地说:列表推导式将for循环和创建新元素的规则合并成一行,并自动实现元素的添加
任务二:使用带if条件的列表推导式
生成一个aList列表,它的元素是nums列表中,元素值大于3的元素平方项。
通过if从nums列表中选择符合条件的元素来组成新的列表
1 | # 普通版本 |
1 | # 推导式版本 |
任务三:二重循环的列表推导式
知识点:嵌套的列表推导式理解
列表推导式包含一个由中括号**[]**组成的表达式。
表达式之后紧跟一个 for循环,之后可以嵌套有零或多个 for或 if语句。
嵌套的多个for或if语句,按从左到右的顺序(外层语句块到内层语句块)来解读代码
生成一个aList列表,它的元素是二维列表nums中,元素值大于0的元素
1 | # 普通版本 |
1 | nums = [[1, -2, 3], [4, 5, -6], [7, -8, 9]] |
知识点:
注意列表推导式中的for和if语句的顺序
列表推导式中,省略了**:**冒号
任务四:多个列表同时遍历的列表推导式
两个列表num1和num2,生成的新列表元素为num1和num2中元素值不同的成对元素组合
1 | # 普通版本(外层与内层语句块注意缩进) |
(x,y)为元组,它是不可变列表,后面章节会学到
1 | num1 = [1, 2, 3, 5] |
任务五:调用函数的列表推导式
列表推导式中可以调用函数
定义函数f:若v能被2整除,则返回v的平方,不能被2整除,返回v+1。具体的函数定义会在后续章节详细讲解
1 | def f(v): |
先运行**f(v)**函数的cell
1 | # 普通版本 |
1 | nums = [[1, 2, 3], [4, 5, 6], [7, 8, 9]] |
任务六:列表推导式的嵌套
1 | nums = [1, 2, 3] |
1 | nums = [1, 2, 3] |
习题:
1、用列表推导式的方法,把列表**[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]**中的每个元素都加100,生成一个新列表
1 | list =[] |
1 | [100, 101, 102, 103, 104, 105, 106, 107, 108, 109] |
2、用列表推导式生成1-20000之间所有能被3整除不能被5整除的数
1 | numbers = [x for x in range(1,20001) if x%3==0 and x%5!=0] |
3、用列表推导式生成列表:**[[0,0,0,0,0],[0,1,2,3,4],[0,2,4,6,8],[0,3,6,9,12]]**
1 | list = [[0,0,0,0,0]] |
1 | [[0, 0, 0, 0, 0], [0, 1, 2, 3, 4], [0, 2, 4, 6, 8], [0, 3, 6, 9, 12]] |
4、3的倍数:创建一个列表,其中包含3~30内能被3整除的数字;再使用一个for循环将这个列表中的数字都打印出来
1 | numbers = list(range(3, 31, 3)) |
1 | 3 |
5、立方:将同一个数字乘三次称为立方。例如,在Python中,2的立方用2**3表示。请创建一个列表,其中包含前10个整数(即1~10)的立方,再使用一个for循环将这些立方数都打印出来
1 | numbers = [] |
1 | 1 |
6、立方解析:使用列表解析生成一个列表,其中包含前10个整数的立方
1 | numbers = [value ** 3 for value in range(1,11)] |
1 | [1, 8, 27, 64, 125, 216, 343, 512, 729, 1000] |
6.2 切片:列表索引
6.2.1 切片的定义
任务一:初识切片
知识点:切片(slide)用于获取列表片段
之前学习了如何使用通过索引来访问列表的单个元素。比如:x[0]
在实际应用中,经常需要访问列表的片段或子集
为此,Python专门设计了切片技术来实现列表元素的一次性批量访问
str字符串、list列表、tupple元组、ndarray多维数组等都支持切片操作
知识点:切片语法
语法:x[start:stop:step]
描述:从列表x中获得子列表,索引范围为**[start stop)左闭右开区间,步长为step**
参数:3个参数必须是整数(包括负整数)
start为起始索引,默认值为0
stop为终止索引,默认值为len(x)
step为步长,默认值为1。不能为0,否则会出错
返回:子列表。从start开始到stop,但不包括stop元素
1 | x = [1, 4, 2, 5, -1, 4, 9, 10] |
任务二:切片不包含stop元素
知识点:常见的逻辑错误(坑)
切片返回的子列表,不包括stop索引元素
例如:x[0:3:1],返回的子列表元素为x[0],x[1],x[2],但不包括x[3]
1 | x = [1, 4, 2, 5, -1, 4, 9, 10] |
6.2.2 (重要) 深入切片
任务一:掌握切片常见用法
知识点:切片的理解
切片的用法与**range(start:stop:step)**类似,3个参数的意义也类似
可以这么理解切片:首先使用range(start:stop:step)生成一组列表元素的索引。然后,再应用于列表x,来获得索引范围为**[start, stop)**的子列表
切片使用较灵活,具体包括如下几种情况:
情况1: 三个参数x[start:stop:step],包含两个:
这是完整的切片表达式
两个:用于分隔三个参数(start、stop、step)
1 | x = [1, 4, 2, 5, -1, 4, 9, 10] |
情况2:两个参数x[start:stop],包含一个:
默认值步长step=1
等价于x[start:stop:1]
1 | x = [1, 4, 2, 5, -1, 4, 9, 10] |
情况3.1:一个参数x[start:],包含一个:
此时,默认值stop=len(x),表示切片的终止于列表末尾。默认值步长step=1
等价于x[start:len(x):1]
1 | x = [1, 4, 2, 5, -1, 4, 9, 10] |
情况3.2****:一个参数x[:stop],包含一个:
此时,默认值start=0,表示切片的起始为0。默认值步长step=1
等价于x[0:stop:1]
1 | x = [1, 4, 2, 5, -1, 4, 9, 10] |
情况4:一个参数x[start],没有:
退化为单索引
此时,stop=start+1,表示切取start指定的那个元素。默认值步长step=1
等价于x[start:start+1:1]
1 | x = [1, 4, 2, 5, -1, 4, 9, 10] |
情况5:没有参数,x[::]或x[:]
如果以上3个参数都未指定,那么它们会被设置默认值:start=0、stop=len(x)和 step=1
此时,为返回的子列表为x本身
等价于x[start=0:len(x):1]
功能:复制列表x
1 | x = [1, 4, 2, 5, -1, 4, 9, 10] |
6.2.3 参数缺省的切片
任务一:掌握参数缺省的切片使用
知识点:
当step>0时为正序,索引可以从正数的0开始从左向右(正序)
当step<0时为逆序,从负数的-1开始从右向左(逆序)
知识点(总结):参数缺省的切片
x[start:stop]:缺省step=1
x[start:]:缺省stop=len(x),step=1
x[:stop]:缺省start=0,step=1
**x[:stop:step]**:
当step>0正序,缺省start=0
当step<0逆序,缺省start=len(x)
**x[start::step]**:
当step>1正序,缺省stop=len(x)
当step<1逆序,缺省stop=-len(x)-1
了解:逆序最后个元素为**-len(x)(即列表第1个元素),由于切片返回的子列表不包括stop元素。因此,为了获得逆序最后个元素,需要设置stop=-len(x)-1**
x[:]和x[::]:缺省start=0,stop=len(x),step=1。相当于复制列表x
1 | x = [1, 4, 2, 5, -1, 4, 9, 10] |
1 | x = [1, 4, 2, 5, -1, 4, 9, 10] |
1 | x = [1, 4, 2, 5, -1, 4, 9, 10] |
1 | x = [1, 4, 2, 5, -1, 4, 9, 10] |
1 | x = [1, 4, 2, 5, -1, 4, 9, 10] |
6.2.4 列表元素的两种拷贝方式
任务一:列表元素的赋值拷贝
知识点:列表拷贝的误区(赋值y=x实现了浅拷贝)
误区:两个列表的拷贝,直接通过赋值语句
list_copy = list_org,通过赋值**=将list_org复制给list_copy**
赋值拷贝:赋值=并不能实现真正的拷贝,只拷贝了变量的地址,而非数据的本身内容。细节请回顾3.2 变量内涵
可以通过**id()**函数查看变量的内存地址
通过a is b逻辑判断变量a和b是否指向同一个数据
赋值拷贝存在的问题:它们的修改会相互影响。即修改其中的一个列表对象,会影响另外个列表
1 | # 赋值拷贝 |
分析:
通过上面的例子看到了通过赋值y=x方式拷贝列表,并不能实现真正的列表元素拷贝
即对x和y列表的改变,会相互影响。真正的拷贝是希望两个列表是独立的,它们的修改互不干扰
通过id()和x is y发现它们本质上是同一个列表对象
任务二:列表元素的切片元素拷贝
知识点:**y = x[:]**实现了列表元素的拷贝
切片元素拷贝:y拷贝的是x的所有元素,而非简单的地址
理解:在内存空间中开辟一个与x同样大小的空间,然后,将x的所有元素依次拷贝,最后将新空间地址赋值给y
1 | # 切片元素拷贝 |
6.2.5 切片的逻辑位置转换公式
任务一:掌握参数为负的切片
知识点:
如果step为负,那么返回的子列表为原列表的逆序
这里先假设start和stop都为正号
子列表正序还是逆序,只与step符号有关,与start和stop无关
正序step>0要求start要位于stop的逻辑左边(start<stop)。否则,返回空列表[]
逆序step<0要求start要位于stop的逻辑右边(start)。否则,返回空列表[]
逆序时,不能缺省step参数。因为,缺省的step=1为正序
1 | x = [1, 4, 2, 5, -1, 4, 9, 10] |
1 | x = [1, 4, 2, 5, -1, 4, 9, 10] |
任务二:掌握逻辑位置转换公式
知识点:start和stop的逻辑位置转换公式
负索引与正索引逻辑位置转换公式:负索引**-n等价于正索引len(x)-n,其中n**为正整数
例如,负索引**-1等价于正索引len(x)-1**
切片的start和stop和可以正负号混用(不建议),但是一定要注意:
正序step>0,要求start要位于stop的逻辑左边。所谓的逻辑左边,可以对负索引先用逻辑位置公式转换为对应的正索引,然后再判断是否满足`start
逆序step<0**,要求**start**要位于**stop**的**逻辑右边**。所谓的**逻辑右边**,可以对负索引先用逻辑位置公式转换为对应的正索引,然后再判断是否满足**start>stop
遇到切片中有负索引,建议先用逻辑位置公式转换为正索引
1 | x = [1, 4, 2, 5, -1, 4, 9, 10] |
1 | x = [1, 4, 2, 5, -1, 4, 9, 10] |
6.2.6 遍历切片
任务一:使用for遍历列表元素
知识点:
切片的类型为列表list,也是可迭代对象,因此可以使用for来遍历它的元素
如果要遍历列表的部分元素,可在for循环中使用切片
1 | x = [1, 4, 2, 5, -1, 4, 9, 10] |
1 | x = [1, 4, 2, 5, -1, 4, 9, 10] |
6.3 元组:不可变列表
6.3.1 元组的定义
任务一:为何需要元组?
知识点:不可变数据集合的需求
列表适用于储存运行期间可以动态增、减、修改的数据集合
Python为列表提供了强大数据处理技术:元素添加和删除、排序、列表遍历、切片、列表推导式等
然而,有些场合需要提供不可变的数据集合,即其中的元素不能被增、减、修改等操作
在现实世界中,很多物体的属性也都是不可被修改
比如课桌的长宽高,出厂之后是不变的
如果在程序运行中,对它们的属性进行修改,这是非法操作
为满足上述需求,Python专门设计了不可变的数据集合类型——元组(tuple)。相对于列表来说,元组是更简单和高效的数据结构。如果需要存储在整个程序生命周期内都不变的一组值,建议使用元组
任务二:元组的定义
知识点:
Python的元组与列表功能类似,但是元组在创建之后,就永远不能被修改了
标识:元组使用小括号**(),列表使用方括号[]**
元组创建:在小括号中添加元素,并使用逗号隔开,小括号可以被省略。例如:nums=(1,2,3)和nums=1,2,3
元组类型:使用type()函数查看,类型为tuple
1 | # 使用()的元组创建 |
任务三:创建空元组
知识点:
不包含任何元素的元组为空元组,用**()**表示
info_tuple = ()
任务四:只含一个元素的元组创建
知识点:
对于只有一个元素的元组,为降低内存消耗,Python使用该元素原类型来代替元组。例如,**data = (123)**为数值类型。
要创建只含有一个元素的元组,在值后面跟必须一个逗号**,。例如,data = (123, )**为元组类型。
适用于带**()和不带()**的元组创建
1 | # 虽带(),但是此时data并非元组 |
6.3.2 元组的基本操作与遍历
任务:使用[]
访问元组元素
知识点:
与列表一样,元组使用带[]的索引来访问元素
第1个元素对应0索引
注意:虽然元组使用**()来标识,但是元组访问依然使用[]而不是()**
在Python中,**()主要用于函数调用,传递参数。位于()之前的一般都是函数名。例如print(‘hello’)**
知识点:
类似列表,元组的索引从0开始
索引就是数据在元组中的位置编号
1 | # 元组支持不同类型的元素 |
任务二:元组的元素统计与索引获取
知识点:
定义元组info = (“zhangsan”, 18, 1.75)
len(元组变量):返回元组长度,值为最大索引n + 1
元组变量.count(值):返回某个元素在元组中出现的次数
元组变量[索引]:根据索引从元组中,获得元素
元组变量.index(值):返回某个值第一次出现在元组的索引
知识点:在Jupyter中,使用Tab智能联想元组类型提供的方法
1 | info_tuple = ("zhangsan", 18, 1.75, "zhangsan") |
任务三:遍历元组元素
知识点:与列表一样,使用for-in循环来遍历元组的元素
1 | info_tuple = ("zhangsan", 18, 1.75) |
6.3.3 元组的应用场景
任务一:元组的主要应用场景
元组的3个主要的应用场景:
函数的参数和返回值,一个函数可以接收任意多个参数,或者一次返回多个数据。有关函数的参数和返回值,会在后续函数章节详细讲解
格式字符串,格式化字符串后面的**()**本质上就是一个元组
让列表不可以被修改,以保护数据安全
任务二:%字符串格式化
知识点:
字符串的格式化方法在开发中非常实用
使用**%操作符,可将元组中的内容,依次**填入到字符串占位符处
后续字符串章节会专门讲解关于字符串的格式化高阶用法
1 | info_tuple = ("小明", 21, 1.85) |
6.3.4 (重要) 元组的不可变性质
任务一:元组的元素值不可被修改
知识点:
元组的元素值不能被修改,即不能进行增、减、删除等操作
语法错误:TypeError: ‘tuple’ object does not support item xxx。tuple对象不支持xxx操作
友情提醒:当出现语法错误,查看错误类型,尝试着自己根据错误提示来排除错误
1 | data = (1, '2', 'python', 4.12) |
任务二:元组变量允许被重新赋值
元组的元素值不能被修改,但是指向元组的变量可以被重新赋值
知识点:
变量存的是指向具体元组数据的地址,而非存元组数据本身。回顾请看变量的内涵
变量的重新赋值仅仅只是更换了一个地址,指向了新的数据空间,而并未对原元素数据做任何修改
可以通过**id()**来查看变量赋值前后的所指向的地址是否发生变化
元组元素不可修改指的是元组的数据空间的元素值不能被修改
因此,如果一定要修改元组的元素,可重新覆盖定义整个元组
1 | data = (1, '2', 'python', 4.12) |
任务三:元组和列表之间的转换
知识点:
**list(元组)**:使用 list 函数可以把元组转换成列表
**tuple(列表)**:使用 tuple 函数可以把列表转换成元组
6.3.5 (重要) 元组的解包和多个返回值函数
任务一:元组的解包
知识点:
通过解包可以对元组的元素进行拆分,并刻赋值给多个变量
左边被赋值的变量个数必须和元组元素个数相同,否则出现解包错误。ValueError: too many values to unpack
通过**len()**查看元组元素个数
1 | data = (1, '2', 'python', 4.12) |
1 | # 元组元素个数必须等于赋值变量个数 |
任务二:使用元组的解包接受多个函数返回值
知识点(补充知识): 函数传递多个返回值
在C++中,函数允许最多传回一个返回值,否则会出错
在Python中,通过元组可以实现传递函数的多个返回值功能。对函数的多个返回值使用元组进行打包,然后在函数调用之处进行解包赋值
1 | #观察divmod()函数 |
任务三:定义拥有多个返回值的函数
知识点:定义拥有多个返回值的函数
元组可以包含多个数据,因此可以使用元组让函数一次返回多个值
类似于元组的创建,函数的返回值如果是元组,那么小括号**()**可以省略
函数的用法后续章节会详细讲解,这里仅为了深入地讲解元组的封包和解包功能
函数定义语法:def 函数名(用逗号,隔开的形参):
冒号**:**不要忘记,后面紧跟着的是被缩进的语句块
def为函数定义的关键字
1 | # 定义函数, 有多个返回值(返回元组) |
任务四:函数的多个返回值处理
知识点:函数的多个返回值处理:
方式1:使用一个变量直接接受函数返回的元组,然后再通过索引方式来访问元组元素
方式2:使用多个变量直接对函数返回值进行解包
1 | # 方式1:用一个变量接受函数返回的元组 |
1 | # 方式2:可使用多个变量,一次接收函数的返回结果 |
6.3.6 实例:利用元组实现变量值的交换
任务一:两个变量值的交换
变量值的相互交换在设计排序算法中,非常重要。下面介绍3种值交换方式,前面两种适用于任何语言,最后种为Python独有
方式1:引入额外的其他变量
1 | x = 6 |
方式2:使用算术运算,仅适用于数值类型的变量
1 | x = 6 |
方式3:使用元组的封包和解包操作(Python独有)。可以省略等号创建元组可以省略**()**
1 | x = 6 |