本章将介绍一个新概念:数据结构。数据结构是以某种方式(如通过编号)组合起来的数据元素(如数、字符乃至其他数据结构)集合。在python中,最基本的数据结构为序列(sequence)。序列中的每个元素都有编号,即其位置或索引,其中第一个元素的索引为0,第二个元素的索引为1,依此类推。在有些变成语音中,从1开始给序列中的元素编号,但从0开始之初相对于序列开头的偏移量。这显得更自然,同时可回绕到序列末尾,用负索引表示序列末尾元素的位置。可能一开始我们会感到别扭,可是久而久之,你就会习惯啦!
本章首先对序列进行概述,然后介绍一些适用于所以序列(包括列表和元组)的操作。这些操作也适用于本章一些实例中将使用的字符串,下一章将全面介绍字符串操作。讨论这些基本知识后,将着手介绍列表,看看它们有什么特别之处,然后塔伦元组。元组是一种特殊的序列,类似于列表,只是不能修改。
2.1 序列概述
python内置了多种序列,本章重点讨论其中最常用的两种:列表和元组。另一种重要的序列是字符串,将在下一章更详细地讨论。
列表和元组的主要不同在于,列表是可以修改的,而元组不可以。这意味着列表适用于需要中途添加元素的情形,而元组适用于出于某种考虑需要禁止修改序列的情形。禁止修改序列通常出于技术方面的考虑,与python的内部工作原理相关,这也是有些内置函数返回元组的原因所在。在我们自己编写程序时,几乎在所有情况下都可使用列表来代替元组。一种例外情况是将元组用作字典键,这将在后面讨论。在这种情况下,不能使用列表来代替元组,因为字典键是不允许修改的。
在需要处理一系列值时,序列很有用。在数据库中,你可能使用序列来表示人,其中第一个元素为姓名,第二个元素为年龄。如果使用列表来表示(所有元素都放在方括号内,并用括号隔开),这类似于下面这样:
>>> edward = ['Edward Gumby' ,42]
>>> john =['john Smith' ,50]
>>> data =[edward ,john]
>>> data
[['Edward Gumby', 42], ['john Smith', 50]]
注意:python支持一种数据结构的基本概念,名为容器(container)。容器基本上就是可包含其他对象的对象。两种主要的容器是序列(如列表和元组)和映射(如字典)。在序列中,每个元素都有编号,而在映射中,每个元素都有名称(也叫键)。
2.2 通用的序列操作
有几种操作适用于所有序列,包括索引、切片、相加、相乘和成员资格检查。另外,python还提供了一些内置函数,可用于确定序列的长度以及找出序列中最大和最小的元素。
2.2.1 索引
序列中的所有元素都有编号——从0开始递增。我们可以这样来访问各个元素:
>>> greeting ='hello'
>>> greeting[0]
'h'
>>> 'hello'[0]#两种用法的效果是一样的
'h'
注意:字符串就是由字符组成的序列。
上面代码的行为称为索引(indexing)。我们可以使用索引来获取元素。这种索引方式适用于所有序列。当你使用复数索引时,python将从右(即从最后一个元素)开始往左数,因此-1就是最后一个元素的位置。
>>> greeting[-1]
'o'
如果函数调用返回一个序列,可直接对其执行索引操作。例如,如果你只想获取用户输入的年份的第4位,可像下面这样做:
>>> fpirth= input('year:')[3]
year:2019
>>> fpirth
'9'
下面我们来做一个小例子,我刚开始看的时候也是一脸懵逼,可是慢慢打出代码后,就发现其实不难啦!!
代码清单2-1 索引操作示例
>>> months = ['January' , 'February' ,'March' ,'April','May','June','July','August','Septemper' ,'October' 'November','December']
>>> endings= ['st', 'nd', 'rd'] + 17 * ['th'] +['st' ,'nd' ,'rd'] +7*['th'] +['st']
>>> endings
['st', 'nd', 'rd', 'th', 'th', 'th', 'th', 'th', 'th', 'th', 'th', 'th', 'th', 'th', 'th', 'th', 'th', 'th', 'th', 'th', 'st', 'nd', 'rd', 'th', 'th', 'th', 'th', 'th', 'th', 'th', 'st']
>>> year=input('Year:')
Year:2019
>>> month =input('Month(1-12): ')
Month(1-12): 4
>>> day = input('Day(1-31): ')
Day(1-31): 9
>>> month_number = int(month)
>>> day_num =int(day)
>>> month_name=months[month_number -1]#python从0索引开始,记得减1
>>> month_name
'April'
>>> ordinal =day +endings[day_num -1]
>>> ordinal
'9th'
>>> print(month_name + ' ' +ordinal +','+year)
April 9th,2019
2.2.2 切片
除了使用索引来访问单个元素外,还可以使用切片(slicing)来访问特定范围内的元素。为此,可使用两个索引,并用冒号分隔:
>>> tag='<a href="http://www.python.org">python web site</a>'
>>> tag[9:30]
'http://www.python.org'
>>> tag[32:-4]#从第32个元素开始到倒数第5个
'python web site'
再看下面的例子,切片适用于提取序列的一部分,其中的编号非常重要:第一个索引时包含的第一个元素的编号,但第二个索引时切片后余下的第一个元素的编号。
>>> num=[1 , 2 , 3, 4, 5, 6, 7, 8, 9, 10]
>>> num[3:6]#序列为3、4、5的数字,不包括6
[4, 5, 6]
>>> num[0:1]#序列为0的数字,不包括1
[1]
>>> num[-3:-1]#从倒数第3个数字起
[8, 9]
简而言之,我们提供两个索引来指定切片的边界,其中第一个索引指定的元素包含在切片内,但第二个索引指定的元素不包含在切片内。
如果我们使用索引0,即到达列表末尾后再前进一步所处的位置,结果将如何呢?
>>> num[-3:0]
[]
>>> num[-3:]
[8, 9, 10]
因为最后一位数字在python中时-1,而不是0,所以我们输入num[-3:0]会出来一个空集,假如我们想输出后三位元素,我们可以输入num[-3:]。
从上面的num[-3:],我们可以发现,切片是可以简写的。
>>> num[-3:]#输出倒数3位数字
[8, 9, 10]
>>> num[:3]#输出第0、1、2序列的元素数字
[1, 2, 3]
>>> num[:]#返回整个序列
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
在执行切片操作时,我们可以显式或隐式地指定起点和终点,但通常省略了另一个参数,即步长。在普通切片中,步长为1.这以为这从一个元素移到下一个元素,因此切片包含起点和终点之间的所有元素。但是同样的,我们可以自行选择步长,当我们选择步长为2时,将从起点和终点之间每隔一个元素提取一个元素。我们来看看下面的例子:
>>> num[:]#如不写,步长默认为1
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
>>> num[::1]
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
>>> num[0:10:1]#前面三种写法是一样的,步长为1
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
>>> num[0:10:2]#步长为2,中间相隔一个元素
[1, 3, 5, 7, 9]
>>> num[0:10:3]#步长为3,中间相隔2个元素。
[1, 4, 7, 10]
>>> num[3:8]
[4, 5, 6, 7, 8]
>>> num[3:8:2]#返回序列为3到7的中间相隔1个的元素
[4, 6, 8]
>>> num[3:8:0]
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ValueError: slice step cannot be zero
我们要注意,步长是不能为0的,否则无法向前移动,但是可以是负数,就是从右向左提取元素。
>>> num[8:3:-1]#取序列为8、7、6、5、4的元素,不包括序列为3
[9, 8, 7, 6, 5]
>>> num[:10:-2]#从右边数没有0
[]
>>> num[10:0:-2]
[10, 8, 6, 4, 2]
>>> num[::-2]#
[10, 8, 6, 4, 2]
>>> num[5::-2]#返回序列为5、4、3、2、1、0的元素
[6, 4, 2]
>>> num[:5:-2]#返回序列为6、7、8、9的元素
[10, 8]
我们要根据我们的需要去索引和切片。要注意几个点,第一个索引依然包含在内,而第二个索引不包含在内。步长为负数时,第一个索引必须必第二个索引大。
2.2.3 序列相加
可以使用加法运算来拼接序列。
>>> [1,2]+[2,3]
[1, 2, 2, 3]
>>> 'hello'+'word'
'helloword'
>>> [1,2]+'hello'
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: can only concatenate list (not "str") to list
从上面报错信息,我们可以知道,不能拼接列表和字符串。就是说,不能拼接不同类型的序列。
2.2.4 乘法
将序列与数相乘时,将重复这个序列x次来创建一个新序列:
>>> 'hello'*5
'hellohellohellohellohello'
>>> [4]*10
[4, 4, 4, 4, 4, 4, 4, 4, 4, 4]
>>> [None]*10#空值也可以这样操作
[None, None, None, None, None, None, None, None, None, None]
2.2.5 成员资格
要检查特定的值是否包含在序列中,可使用运算符in。它检查是否满足指定的条件,并返回相应的值:满足时返回True,不满足时返回Falese。这样的运算符称为布尔运算符,而真假值称为布尔值。
>>> a='hello'
>>> 'h' in a
True
>>> b=['abc','cdf']
>>> input('input some letter:') in b
input some letter:abc
True
下面我们来模拟一下检查用户名和用户号码的场景!!!
database = [
['albert', '1234'],
['dilbert', '4242'],
['smith', '7524'],
['jones', '9843'] ]
username = input('User name: ')
pin = input('PIN code: ')
if [username, pin] in database:
print('Access granted')
else: print('wrong')
上面是代码,把用户名和用户号码写在一列列表中
User name: smith
PIN code: 7524
Access granted
输入名字和用户名号码后,如果正确会返回Access granted
User name: smith
PIN code: 9843
wrong
如果输入错误不对应的话,就会返回wrong
下面介绍几个基本的函数,看看代码就懂啦!!!
>>> a = [100,90, 80]
>>> len(a)#求长度
3
>>> max(a)#最大值
100
>>> min(a)#最小值
80
>>> max(2,3,4,5)
5
>>> min(2,3,4,5)
2
2.3 列表:python的主力
2.3.1 函数list
>>> list('hello')
['h', 'e', 'l', 'l', 'o']
我们可以将任何序列,当然不仅仅是字符串作为list的参数。
2.3.2 基本的列表操作
接下来将介绍一些修改列表的方式:给元素赋值、删除元素、给切片赋值以及使用列表的方法。(并非所有列表方法都会修改列表。
1、修改列表:给元素赋值
>>> x=[1,1,1]
>>> x[1]
1
>>> x[1]=2修改第二位元素
>>> x
[1, 2, 1]
看上面的例子很容易看懂啦!!!另外我们要主义,不可以给超过列表长度的元素赋值哦!!!
2、删除元素
>>> x
[1, 2, 1]
>>> del x[2]#删除第三位元素
>>> x
[1, 2]
3、给切片赋值
切片是一个很强大的功能,而能够给切片赋值让这项功能显得更加强大。看例子!!
>>> name = list('hello')
>>> name
['h', 'e', 'l', 'l', 'o']
>>> name[2:] =list ('hi')
>>> name
['h', 'e', 'h', 'i']
>>> name1 = list ('hello')
>>> name1[2:4] = list('hi')
>>> name1
['h', 'e', 'h', 'i', 'o']
是不是很酷!!!更改第三位元素开始的字母!!!而且可以根据需求来替换长度与其不同的序列。
下面我们来看看使用切片赋值可以替换原有元素的情况下插入新元素。
>>> num=[1,2]
>>> num[1:1]=[9,9,9]
>>> num
[1, 9, 9, 9, 2]
>>> num[1:4]=[]
>>> num
[1, 2]
当然我们也可以插入空切片!!!
2.3.3 列表方法
方法是与对象(列表、数、字符串等)联系紧密的函数。通常像下面这样调用方法:
object.method(arguments)
1、append
>>> lis = [1,2,4]
>>> lis.append(5)
>>> lis
[1, 2, 4, 5]
这里要注意一下,list是一个内置函数,如果我们用内置函数来命名的话,就无法调用那个函数了。
2、clear
>>> lis
[1, 2, 4, 5]
>>> lis.clear()#清除法一
>>> lis
[]
>>> lis=[1,2,4,5]
>>> lis[:]=[]#清除法二
>>> lis
[]
3、copy
方法copy复制列表。常规复制不只是会复制,还会将两个列表关联起来。看代码:
>>> a=[1 ,2 ,3]
>>> b=a
>>> b[1]
2
>>> b
[1, 2, 3]
>>> b[1]=4
>>> b
[1, 4, 3]
>>> a
[1, 4, 3]
从上面代码我们可以看到,当我们赋值后,b的改变会带着a一起改变。下面让我们来看看copy的作用会不会有什么不同
>>> a= [1 ,2, 3]
>>> b = a.copy()
>>> b[1]
2
>>> b
[1, 2, 3]
>>> a
[1, 2, 3]
>>> b[1]=4
>>> b
[1, 4, 3]
>>> a
[1, 2, 3]
如果我们用copy的话,b的改变就不会影响到a了
>>> a
[1, 2, 3]
>>> c = a[:]
>>> c
[1, 2, 3]
>>> c[1]=4
>>> c
[1, 4, 3]
>>> a
[1, 2, 3]
>>> d = list(a)
>>> d
[1, 2, 3]
>>> d[1] =4
>>> d
[1, 4, 3]
>>> a
[1, 2, 3]
从上面的代码我们可以知道,a[:]或list(a),也同样可以复制a,而且不会被改变。
4、count
方法count计算指定的元素在列表中出现了多少次。
>>> ['t','0','l','t'].count('to')
0
>>> x= [[1,2] ,1,2,1,[2,3]]
>>> x.count(1)
2
5.extend
方法extend可以附加在列表末尾。我们可以用这个方法向一个列表扩展另一个列表。
>>> a= [1,2]
>>> b = [2 , 3]
>>> a.extend(b)#直接扩展到列表a后
>>> a
[1, 2, 2, 3]
>>> b
[2, 3]
>>> c=[1,2]
>>> b+c#不改变原列表
[2, 3, 1, 2]
>>> b
[2, 3]
>>> c
[1, 2]
>>> b[len(b):]=c#用切片扩展在b列表后,可读性比较复杂
>>> b
[2, 3, 1, 2]
6、index
方法index就在列表中查找指定第一次出现的索引。
>>> hi = ['h' ,'e','l', 'l', 'o']
>>> hi.index('e')#返回所在位置
1
>>> hi.index('l')#一旦找到,就会停止索引,不会继续向下索引
2
>>> hi.index('z')#没有z这个元素
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ValueError: 'z' is not in list
7、insert
方法insert用于将一个对象插入列表。
>>> num = [1 ,4 ,3,6]
>>> num.insert(2,'all')
>>> num
[1, 4, 'all', 3, 6]
>>> num = [1 ,4 ,3,6]
>>> num[2:2]=['all']#同样也可以用切片完成
>>> num
[1, 4, 'all', 3, 6]
8、pop
方法pop是从列表中删除一个元素,并返回这一元素。
>>> num
[1, 4, 'all', 3, 6]
>>> num.pop()#若不指定位置,就是删除最后一位元素并返回
6
>>> num
[1, 4, 'all', 3]
>>> num.pop(1)
4
>>> num
[1, 'all', 3]
使用pop可实现一种常见的数据结构——栈(stack)。栈就像一叠盘子,你可在上面添加盘子,还可从上面取走盘子。最后加入的盘子最先取走,着被称为后进先出(LIFO)
9、remove
方法remove用于删除第一个为指定的元素。
>>> hi = ['h' ,'e','l', 'l', 'o']
>>> hi.remove('l')
>>> hi
['h', 'e', 'l', 'o']
remove和pop不同,remove是就地修改并且不返回值,而pop会返回被删掉的值。
10、reverse
方法reverse按相反的顺序排列列表中的元素
>>> num = [ 1 , 4 , 3 , 6 ]
>>> num.reverse()
>>> num
[6, 3, 4, 1]
11、sort
方法sort用于对列表就地排序。就地排序就意味着对原来的列表进行修改,使其元素按顺序排列。
>>> num
[6, 3, 4, 1]
>>> num.sort()
>>> num
[1, 3, 4, 6]
>>> x =[1,2,3]
>>> y=x.sort()
>>> y
none
上面的y出现空值,是因为sort是只修改x而且不返回任何值的。所以我们要注意,不可以向上面那样去赋值。如果我们非得要y得到x的排序,但是x又要不变,我们可以像下面那样做。
>>> x =[ 9, 5, 7,3 ,1]
>>> y=x.copy()#第一种方法
>>> y.sort()
>>> y
[1, 3, 5, 7, 9]
>>> x
[9, 5, 7, 3, 1]
>>> y2=sorted(x)#第二种方法
>>> x
[9, 5, 7, 3, 1]
>>> y2
[1, 3, 5, 7, 9]
如果想得到相反的顺序排列,可以用reverse函数。
>>> y2.reverse()
>>> y2
[9, 7, 5, 3, 1]
12、高级排序
方法sort接收两个可选参数:key和reverse。这两个参数通常是按名称指定的,称为关键字参数。参数key类似于参数cmp:你将其设置为一个用于排序的函数。然而,不会直接使用这个函数来判断一个元素是否比另一个元素小,而是使用它来为每个元素创建一个键,再根据这些键对元素进行排序。因此,要根据长度对元素进行排序,可将参数key设置为函数len。
>>> x=['abc','a','hdgd','gasgfsj','jdsg','djs']
>>> x.sort(key=len)
>>> x
['a', 'abc', 'djs', 'hdgd', 'jdsg', 'gasgfsj']
对于另一个关键字参数reverse,只需将其指定为一个真值(True或False),以指出是否要按相反的顺序对列表进行排序。
>>> x =[ 9, 5, 7,3 ,1]
>>> x.sort(reverse=False)
>>> x
[1, 3, 5, 7, 9]
>>> x.sort(reverse=True)
>>> x
[9, 7, 5, 3, 1]
2.4 元组:不可修改的序列
与列表一样,元组也是序列,唯一的差别在于元组是不能修改的,慢慢我们也可以注意到,字符串也是不能修改的。下面让我们从创建元组开始吧
>>> 1,2,3#创建元组
(1, 2, 3)
>>> (1,2,3)#创建元组
(1, 2, 3)
>>> ()
()
>>> 42
42
>>> 42,#即使只有一个值,也必须再它后面加上逗号
(42,)
>>> (42,)#当然,以免出错,还是加上括号比较好
(42,)
>>> (42)#如果没有逗号,即使加上括号也没用
42
>>> 3*(40+1)
123
>>> 3*(40+1,)
(41, 41, 41)
下面我们来看看tuple函数,其工作原理和list很像:将一个序列作为参数,并将其转换为元组。如果参数已经是元组,就原封不动地返回它。
>>> a= [1 , 2, 3]
>>> tuple(a)#不会改变a的原本类型
(1, 2, 3)
>>> a#a还是列表
[1, 2, 3]
>>> tuple('hello')
('h', 'e', 'l', 'l', 'o')
>>> tuple((1 ,2 ,3) )
(1, 2, 3)
>>> a[1:2]
[2]
>>> b=tuple(a)
>>> a[1:2]#列表返回其中一个元素
2
>>> b
(1, 2, 3)
>>> b[1:2]#元组返回其中一个元素
(2,)
2.5 小结
序列:序列是一种数据结构,其中的元素带编号(从0开始)。列表、字符串和元组都属于序列,其中列表是可变的,元组和字符串是不可变的(一旦创建内容就是固定的)。套方位序列的一部分,可以使用切片操作:提供两个指定切片起始和结束位置的索引。要修改列表,可给其元素赋值,也可使用赋值语句给切片赋值。
成员资格:要确定特定的值是否包含再序列(或其他容器)中,可使用运算符in。将运算符in用于字符串时情况比较特殊——这样可查找子串。
网友评论