好好学Python:从零基础到项目实战
上QQ阅读APP看书,第一时间看更新

3.1 通用序列操作

在讲解列表和元组之前,本节先介绍Python中序列的通用操作,这些操作在列表和元组中都会用到。

Python中所有的序列都可以进行一些特定操作,包括索引(indexing)、分片(slicing)、序列相加(adding)、乘法(multiplying)、成员资格、长度、最小值和最大值。

3.1.1 索引

序列是Python中最基本的数据结构。序列中的每个元素都有一个数字下标,代表它在序列中的位置,这个位置就是索引。

在序列中,第一个元素的索引下标是0,第二个元素的索引下标是1,以此类推,直到最后一个元素。

比如上面“序列号”大巴的所有同学,就已经被分配了从0到30的索引下标。我们也可以称Python快乐学习班的所有同学已经组成了一个序列,每个同学的序号代表了他在序列中的位置。

序列中所有元素都是有编号的,从0开始递增。可以通过编号分别对序列的元素进行访问。

比如对于“序列号”大巴上的第二组的成员,他们的序号分别是5、6、7、8、9,将这5个序号放在一个字符串中,该字符串赋给变量group_2,意为第二组。

现对group_2做如下操作:

>>> group_2='56789'  #定义变量group_2,并赋值56789
>>> group_2 [0]      #根据编号取元素,使用格式为:在方括号中输入所取元素的编号值
'5'
>>> group_2 [1]
'6'
>>> group_2 [2]
'7'

由输出结果可以看到,序列中的元素下标是从0开始的,从左向右,从0开始依自然顺序编号,元素可以通过编号访问。获取元素的方式为:在定义的变量名后加方括号,在方括号中输入所取元素下标的编号值。

就如“序列号”大巴上的所有同学,目前已经从0编号到30,每个序号对应一位同学。程序中的序列也是如此。

这里的编号就是索引,可以通过索引获取元素。所有序列都可以通过这种方式进行索引。

提示

字符串本质是由字符组成的序列。索引值为0的指向字符串中的第一个元素。比如在上面的示例中,索引值为0指向字符串56789中的第一个字符5,索引值为1指向字符6,索引值为2指向字符7,等等。

上面的示例是从左往右顺序通过下标编号获取序列中的元素,也可以通过从右往左的逆序方式获取序列中的元素,其操作方式如下:

>>> group_2[-1]
'9'
>>> group_2[-2]
'8'
>>> group_2[-3]
'7'
>>> group_2[-4]
'6'

由输出结果可以看到,Python的序列也可以从右开始索引,并且最右边的元素索引下标值为-1,从右向左逐步递减。

在Python中,从左向右索引称为正数索引,从右向左索引称为负数索引。使用正数索引时,Python从索引下标为0的元素开始计数,往后依照正数自然数顺序递增,直到最后一个元素。使用负数索引时,Python会从最后一个元素开始计数,从-1开始依照负数自然数顺序递减,最后一个元素的索引编号是-1。

提示

在Python中,做负数索引时,最后一个元素的编号不是-0,与数学中的概念一样,-0=0,-0和0都指向序列中下标为0的元素,即序列中的第一个元素。

从上面的几个示例可以看到,进行字符串序列的索引时都定义了一个变量,其实不定义变量也可以。下面来看一个例子,在交互模式下输入:

>>> '56789'[0]
'5'
>>> '56789'[1]
'6'
>>> '56789'[-1]
'9'
>>> '56789'[-2]
'8'

由输出结果可以看到,对序列可以不定义变量,直接使用索引。直接使用索引操作序列的效果和定义变量的效果是一样的。

读者在实际使用时可以依照个人的习惯操作,但建议读者定义变量,因为定义变量只需要赋一次值,后续直接操作变量即可。

如果函数返回一个序列,是否可以直接对结果进行索引操作呢?在此以input输入函数作为示例,在交互模式下输入:

>>> try_fun=input()[0]
test
>>> try_fun
't'

这里直接对函数的返回结果进行了索引操作。此处提前引入了函数和input输入函数的概念,稍做了解即可。

注意,使用索引既可以进行变量的引用操作,也可以直接操作序列,还可以操作函数的返回序列。

3.1.2 分片

序列的索引用来对单个元素进行访问,但若需要对一个范围内的元素进行访问,使用序列的索引进行操作就相对麻烦了,这时我们就需要有一个可以快速访问指定范围元素的索引实现。

Python中提供了分片的实现方式,所谓分片,就是通过冒号相隔的两个索引下标指定索引范围。

比如“序列号”大巴上的同学被分成了6组,若把所有同学的序号放在一个字符串中,若想要取得第二组所有同学的序号,根据前面的做法,就需要从头开始一个一个下标地去取,这样做起来不但麻烦,也耗时。若使用分片的方式,则可以快速获取所有同学的序号。

把所有同学的序号放在一个字符串中,各个序号使用逗号分隔,现要取得第二组所有同学的序号并打印出来。在交互模式下输入:

    >>> student='0,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'
    >>> student[10:19]           #取得第二组所有同学的序号,加上逗号分隔符,需要取得10个字符
    '5,6,7,8,9'
    >>> student[-17:-1]          #负数表明从右开始计数,取得最后一组所有6名同学的序号
    '25,26,27,28,29,3'

由操作结果可以看到,分片操作既支持正数索引,也支持负数索引,并且对于从序列中获取指定部分元素非常方便。

分片操作的实现需要提供两个索引作为边界,第一个索引下标所指的元素会被包含在分片内,第二个索引下标的元素不被包含在分片内。这个操作有点像数学里的a≤x<b,x是我们需要得到的元素,a是分片操作中的第一个索引下标,b是第二个索引下标,b不包含在x的取值范围内。

接着上面的示例,假设需要得到最后一组所有6名同学的序号,使用正数索引可以这样操作:

    >>> student='0,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'
    >>> student[66:83]  #取得最后一组所有6名同学的序号
    '25,26,27,28,29,30'

由输出结果可以看到,很方便地得到了最后一组所有6名同学的序号。

观察得到的结果,使用正数索引得到的最后一组所有6名同学的序号和使用负数索引得到最后一组所有6名同学的序号有一些差异,使用正数索引得到的结果中,最后的两个字符是30,而使用负数索引得到的结果中,最后一个字符是3,没有30这个字符串存在。为什么结果会不一致?我们观察结果得知,是使用负数索引的结果不对。

使用负数索引得到的结果没有输出最后一个元素。我们尝试使用索引下标0作为最后一个元素的下一个元素,输入如下:

>>> student[-17:0]
''

结果没有输出最后一个元素。再试试使用索引0作为最后一个元素的下一个元素,输入如下:

>>> number[-3: 0]
 []

输出结果有点奇怪,返回的是一个空字符串。这是为什么?

在Python中,只要在分片中最左边的索引下标对应的元素比它右边的索引下标对应的元素晚出现在序列中,分片结果返回的就会是一个空序列。比如在上面的示例中,索引下标-17代表字符串序列中倒数第17个元素,而索引下标0代表第1个元素,倒数第17个元素比第1个元素晚出现,即排在第1个元素后面,所以得到的结果是空序列。

那怎么通过负数索引的方式取得最后一个元素呢?

Python提供了一条捷径,使用负数分片时,若要使得到的分片结果包括序列结尾的元素,只需将第二个索引值设置为空即可。在交互模式下输入:

>>> student[-17:]  #取得最后一组所有6名同学的序号
 '25,26,27,28,29,30'

由输出结果看到,此时使用负数索引得到的结果和使用正数索引的结果已经一致了。

正数索引是否可以将第2个索引值设置为空呢,会得到怎样的结果?在交互模式下输入:

>>> student[66:]  #取得最后一组所有6名同学的序号
'25,26,27,28,29,30'

由输出结果可以看到,正数索引也可以将第2个索引值设置为空,结果是会取得第1个索引下标之后的所有元素。

如果将分片中的两个索引值都设置为空,所得的结果又是怎样的呢?在交互模式下输入:

    >>> student[:]  #取得整个数组
    '0,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'

由输出结果可以看到,将分片中的两个索引都设置为空,得到的结果是整个序列值,这种操作其实等价于直接打印出该变量。

进行分片时,分片的开始和结束点都需要指定(无论是直接还是间接),用这种方式取连续的元素没有问题,但若要取序列中不连续的元素就比较麻烦,或者直接不能操作。

比如要取一个整数序列中的所有奇数,以一个序列的形式展示出来,用前面当前所学的方法就不能实现了。

这里我们先引入列表的概念,首先介绍创建列表,关于列表的更多内容会在下一节中展开介绍。

创建列表和创建普通变量一样,用一对方括号括起来就创建了一个列表,列表里面可以存放数据或字符串,数据或字符串之间用逗号隔开,逗号隔开的各个对象就是列表的元素,列表中的元素下标从0开始。以下示例就是创建了一个列表:

>>> number[0: 10: 1]
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

由上面的示例可以看到,分片包含另一个数字。这种方式就是步长的显式设置。看起来和隐式设置步长没什么区别,得到的结果也和之前一样。但若将步长设置为比1大的数,结果会怎样呢?请看以下示例:

    >>>
student=[0,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]

对student列表做如下操作,在交互模式下输入:

    >>> student
    [0, 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]
    >>> student[0]
    0
    >>> student[1:4]
    [1,2, 3]
    >>> student[-3:-1]
    [28, 29]
    >>> student[-3:]
    [28, 29, 30]
    >>> student[:]
    [0, 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]

接下来我们看看如何从student中取得所有的奇数。

对于上面描述的情况,Python为我们提供了另一个参数——步长(step length),该参数通常是隐式设置的。在普通分片中,步长默认是1。分片操作就是按照这个步长逐个遍历序列中的元素,遍历后返回开始和结束点之间的所有元素。也可以理解为默认步长是1,在交互模式下输入:

>>> student[0:10:1]
[0,1, 2, 3, 4, 5, 6, 7, 8, 9]

由输出结果可以看到,分片包含另一个数字。这种方式就是步长的显式设置。将步长设置为1时得到的结果和不设置步长时得到的结果是一致的。但若将步长设置为比1大的数,得到的结果会怎样呢?交互模式中输入:

>>> student[0:10:2]
[0, 2, 4, 6, 8]

由输出结果可以看到,将步长设置为2时,所得到的是偶数序列,若想要得到奇数序列该怎么办呢?在交互模式下尝试如下:

>>> student[1:10:2]
[1, 3, 5, 7, 9]

由输出结果可以看到,所得到的结果就是我们前面想要的奇数序列。

步长设置为大于1的数时,会得到一个跳过某些元素的序列。例如,我们上面设置的步长为2,得到的结果序列是从开始到结束,每个元素之间隔1个元素的结果序列。还可以这样使用:

>>> student[:10:3]
[0, 3, 6, 9]
>>> student[2:6:3]
[2,5 ]
>>> student[2:5:3]
[2]
>>> student[1:5:3]
[1, 4]

由输出结果可以看到,步长的使用方式是非常很灵活的。可以根据自己的需要,非常便利地从列表序列中得到自己想要的结果序列。

除了上面的使用方式,还可以设置前面两个索引为空。操作如下:

>>> student[::3]
[0, 3, 6, 9, 12, 15, 18, 21, 24, 27, 30]

上面的操作将序列中每3个元素的第1个提取出来,前面两个索引都设置为空。如果将步长设置为0,会得到什么结果呢?在交互模式下输入:

由输出结果可以看到,程序执行出错,错误原因是步长不能为0。

既然步长不能为0,那步长是否可以为负数呢?请看下面的例子:

    >>> student[10:0:-2]
    [10, 8, 6, 4, 2]
    >>> student[0:10:-2]
    []
    >>> student[::-2]
    [30, 28, 26, 24, 22, 20, 18, 16, 14, 12, 10, 8, 6, 4, 2, 0]
    >>> student[5::-2]
    [5, 3, 1]
    >>> student[:5:-2]
    [30, 28, 26, 24, 22, 20, 18, 16, 14, 12, 10, 8, 6]
    >>> student[::-1]
    [30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12,
11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0]
    >>> student[10:0:-1]             #第二个索引为0,取不到序列中的第一个元素
    [10, 9, 8, 7, 6, 5, 4, 3, 2,1]
    >>> student[10::-1]              #设置第二个索引为空,可以取到序列的第一个元素
    [10, 9, 8, 7, 6, 5, 4, 3, 2, 1,0]
    >>> student[2::-1]           #设置第二个索引为空,可以取到序列的第一个元素
    [2, 1, 0]
    >>> student[2:0:-1]           #第二个索引为0,取不到序列中的第一个元素
    [2,1]

查看上面的输出结果,使用负数步长时的结果跟使用正数步长的结果是相反的。

这就是Python中正数步长和负数步长的不同之处。对于正数步长,Python会从序列的头部开始从左向右提取元素,直到序列中的最后一个元素;而对于负数步长,则是从序列的尾部开始从右向左提取元素,直到序列的第一个元素。正数步长必须让开始点小于结束点,否则得到的结果序列是空的;而负数步长必须让开始点大于结束点,否则得到的结果序列也是空的。

提示

使用负数步长时,要取得序列的第一个元素,即索引下标为0的元素,需要设置第二个索引为空。

3.1.3 序列相加

序列支持加法操作,使用加号可以进行序列的连接操作,在交互模式下输入:

>>> [1, 2, 3] + [4, 5, 6]
[1, 2, 3, 4, 5, 6]
>>> a = [1, 2]
>>> b = [5, 6]
>>> a + b
[1, 2, 5, 6]
>>> s = 'hello,'
>>> w = 'world'
>>> s + w
'hello,world'

由输出结果可以看到,数字序列可以和数字序列通过加号连接,连接后的结果还是数字序列;字符串序列也可以通过加号连接,连接后的结果还是字符串序列。

数字序列是否可以和字符串序列相加呢,相加的结果又是怎样的呢?在交互模式下输入:

>>> [1, 2] + 'hello'
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: can only concatenate list (not "str") to list
>>> type([1, 2])            #取得[1,2]的类型为list
<class 'list'>
>>> type('hello')           #取得hello的类型为字符串
<class 'str'>

由输出结果可以看到,数字序列和字符串序列不能通过加号连接。错误提示的信息是:列表只能和列表相连。

提示

只有类型相同的序列才能通过加号进行序列连接操作,不同类型的序列不能通过加号进行序列连接操作。

3.1.4 乘法

在Python中,序列的乘法和我们在数学中学习的乘法需要分开理解。

在Python中,用一个数字n乘以一个序列会生成新的序列。在新的序列中,会将原来的序列将首尾相连重复n次,得到一个新的变量值,赋给新的序列,这就是序列中的乘法。在交互模式下输入:

>>> 'hello' * 5
'hellohellohellohellohello'
>>> [7] * 10
[7, 7, 7, 7, 7, 7, 7, 7, 7, 7]

由输出结果可以看到,序列被重复了对应的次数首尾相连,而不是做了数学中的乘法运算。

在Python中,序列的乘法有什么特殊之处呢?

如果要创建一个重复序列,或要重复打印某个字符串n次,就可以像上面的示例一样乘以一个想要得到的序列长度的数字,这样可以快速得到需要的列表,非常方便。

空列表可以简单通过两个方括号([])表示,表示里面什么东西都没有。如果想创建一个占用10个或更多元素的空间,却不包括任何有用内容的列表,该怎么办呢?可以像上面的示例一样乘以10或对应的数字,得到需要的空列表,也很方便。

如果要初始化一个长度为n的序列,就需要让每个编码位置上都是空值,此时需要一个值代表空值,即里面没有任何元素,可以使用None。None是Python的内建值,确切含义是“这里什么也没有”。例如,在交互模式下输入:

>>> sq=[None] * 5  #初始化sq为含有5个None的序列
>>> sq
[None, None, None, None, None]

由输出可以看到,Python中的序列乘法可以帮助我们快速做一些初始化操作。通过序列的乘法做重复操作、空列表和None初始化的操作十分方便。

3.1.5 成员资格

所谓成员资格,是指某个序列是否是另一个序列的子集,该序列是否满足成为另一个序列的成员的资格。

为了检查一个值是否在序列中,Python为我们提供了in这个特殊的运算符。in运算符和前面讨论过的运算符有些不同。in运算符用于检验某个条件是否为真,并返回检验结果,检验结果为真,则返回True,为假,则返回False。这种返回运算结果为True或False的运算符称为布尔运算符,返回的真值称为布尔值。关于布尔运算符的更多内容会在后续章节中进行介绍。

下面看看in运算符的使用示例,在交互模式下输入:

>>> greeting = 'hello,world'
>>> 'w' in greeting   #检测w是否在字符串中
True
>>> 'a' in greeting
False
>>> users = ['xiaomeng', 'xiaozhi', 'xiaoxiao']
>>> 'xiaomeng' in users  #检测字符串是否在字符串列表中
True
>>> 'xiaohuai' in users
False
>>> numbers = [1, 2, 3, 4, 5]
>>> 1 in numbers  #检测数字是否在数字列表中
True
>>> 6 in numbers
False
>>> eng = '** Study python is so happy!**'
>>> '**' in eng  #检测一些特殊字符是否在字符串中
True
>>> '$' in eng
False
>>> 'a' in numbers
False
>>> 3 in greeting
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'in <string>' requires string as left operand, not int

由上面的输出结果可以看到,使用in可以很好地检测字符或数字是否在对应的列表中。

通过代码示例同时也可以看出,数字类型不能在字符串类型中使用in进行成员资格检测,检测时会报错误;字符串类型可以在数字列表中使用in进行成员资格检测,检测时不会报错误。

3.1.6 长度、最小值和最大值

Python为我们提供了快速获取序列长度、最大值和最小值的内建函数,对应的内建函数分别为len、max和min。

这3个函数该怎么使用呢?在交互模式下输入:

由输出结果可以看到,len函数返回序列中所包含元素的个数,也称为序列长度。个数统计是从1开始的,要注意和索引下标区分开,如果用最大元素个数的数值作为索引下标去获取最后一个元素,结果会报错,这是因为索引下标是从0开始的,最大元素个数减去1后得到的数值才是最大的索引下标。

max函数和min函数分别返回序列中值最大和值最小的元素。

在上面的示例中,前面几个函数的输入参数都是序列,可以理解为直接对序列做计算操作。而后面的两个max和min函数中,传入的参数不是一个序列,而是多个数字,在这种情况下,max函数的操作方式是直接求取多个数字中的最大值,min函数的操作方式是直接求取多个数字中的最小值。