(董付国)Python 学习笔记---Python序列(1)

1.Python序列概述:

  • Python序列类似于其他语言中的数组,但功能要强大很多。
  • Python中常用的序列结构有列表,元组,字符串,字典,集合以及range等对象也支持很多类似的操作。
  • 列表、元组、字符串支持双向索引,第一个元素下标为0,第二个元素下标为1,以此类推;最后一个元素下标为-1,倒数第二个元素下标为-2,以此类推。

1.1列表

  • 列表是Python中内置有序可变序列,列表中所有元素放在一对中括号“[ ]”中,并使用逗号分隔开;
  • 当列表元素增加或删除时,列表对象自动进行扩展或收缩内存,保证元素之间没有缝隙;在列表的中间位置进行插入或删除元素时,效率是比较低的,因为会涉及到大量的元素移动。
  • 在Python中,一个列表中的数据类型可以各不相同,可以同时分别为整数、实数、字符串等基本类型,甚至是列表、元组、字典、集合以及其他自定义类型的对象。

列表常用方法:
在这里插入图片描述
1.1.1列表创建与删除

  • 使用“=”直接将一个列表赋值给变量即可创建列表对象
  • 也可以使用list()函数将元组、range对象、字符串或其他类型的可迭代对象类型的数据转换为列表。
  • 当不再使用时,使用del命令删除整个列表,如果列表对象所指向的值不再有其他对象指向,Python将同时删除该值。

1.1.2列表元素的增加
(1)可以使用“+”运算符将元素添加到列表中。

>>> a_list=[1,2,3]
>>> a_list=a_list+[4]
>>> list(a_list)
[1, 2, 3, 4]

严格意义上讲,这并不是真正意义上的添加元素,而是创建一个新列表,并将原列表中的元素和新元素一次复制到新列表的内存空间。由于涉及大量元素的复制,该操作速度较慢,在涉及大量元素添加时不建议使用。
(2)使用列表对象的append()方法,原地(不改变列表在内存中的首地址)修改列表,是真正意义上的列表在尾部添加元素,速度较快。

>>> a_list=[2,3,4,5]
>>> a_list.append(9)
>>> a_list
[2, 3, 4, 5, 9]

现在,我们讲上述两种列表元素的增加方法所用时间进行一个比较:
方法一:

>>> import time
>>>
>>> result=[]
>>> start=time.time()
>>> for i in range(10000):
...     result=result+[i]
...
>>> print(len(result),',',time.time()-start)
10000 , 79.22770667076111**

方法二:

>>> import time
>>>
>>> result=[]
>>> start=time.time()
>>> for i in range(10000):
...     result.append(i)
...
>>> print(len(result),',',time.time()-start)
10000 , 69.65966367721558

(3)Python采用的是基于值的自动内存管理方式,当为对象修改值时,并不是真的直接修改变量的值,而是使变量指向新的值,这对于Python所有类型的变量都是一样的。

>>> a=[1,2,3]
>>> id(a)
2109152147912
>>> a=[1,2]
>>> id(a)
2109151452168

(4)列表中包含的是元素值的引用,而不是直接包含元素值。如果是直接修改序列变量的值,则与Python普通变量的情况是一样的,而如果是通过下标来修改序列中元素的值或通过可变序列对象自身提供的方法来增加或删除元素时,序列对象在内存中的起始地址是不变的,仅仅是被改变值的元素地址发生变化,也就是所谓的“原地操作”。

>>> a=[1,2,3]
>>> id(a)
2109152147912
>>> a=[1,2]
>>> id(a)
2109151452168
>>>
>>>
>>> a=[1,2,4]
>>> b=[1,2,3]
>>> a==b
False
>>> id(a)==id(b)
False
>>> id(a[0])==id(b[0])
True
>>> a=[1,2,3]
>>> id(a)
2109151640520
>>> a.append(4)
>>> id(a)
2109151640520
>>> a.remove(3)
>>> a
[1, 2, 4]
>>> id(a)
2109151640520
>>> a[0]=5
>>> a
[5, 2, 4]
>>> id(a)
2109151640520

(5)使用列表对象的extend()方法可以将另一个迭代对象的元素添加至该列表对象的尾部。通过extend()方法来增加列表元素也不改变其内存首地址,属于原地操作

>>> a=[1,2,3]
>>> a.extend([7,8,9])
>>> a
[1, 2, 3, 7, 8, 9]
>>> id(a)
2109152147912
>>> a.extend([11,12])
>>> a
[1, 2, 3, 7, 8, 9, 11, 12]
>>> id(a)
2109152147912

使用列表对象的insert()方法将元素添加至列表的制定位置,也是原地操作

>>> a
[1, 2, 3, 7, 8, 9, 11, 12]
>>> a.insert(3,4)
>>> a
[1, 2, 3, 4, 7, 8, 9, 11, 12]
>>> id(a)
2109152147912

注:应尽量从列表尾部进行元素的增加与删除操作。

  • 列表的insert()可以在列表的任意位置插入元素,但由于列表的自动内存管理功能,insert()方法会涉及到插入位置之后所有元素的移动,这回影响处理速度。
  • 类似的还有后面介绍的remove()方法以及使用pop()函数弹出列表非尾部元素和使用del命令删除列表非尾部元素的情况。

对比Insert()和Append()的运行时间:

>>> import time
>>>
>>> start=time.time()
>>> a=[]
>>> for i in range(10000):
...     a.insert(0,i)
...
>>> print(len(a),',',time.time()-start)
10000 , 86.43514490127563
>>> import time
>>>
>>> start=time.time()
>>> a=[]
>>> for i in range(10000):
...     a.append(i)
...
>>> print(len(a),',',time.time()-start)
10000 , 72.05369424819946

在IDLE环境下运行:

import time

def Insert():
    a=[]
    for i in range(10000):
        a.insert(0,i)

def Append():
    a=[]
    for i in range(10000):
        a.append(i)

start=time.time()
for i in range(10):
    Insert()
print('Insert:',time.time()-start)

start=time.time()
for i in range(10):
    Append()
print('Append:',time.time()-start)

运行结果:

Insert: 0.9944267272949219
Append: 0.038977861404418945

可见Append比Insert要快很多~
(6)使用乘法来扩展列表对象,将列表与整数相乘,生成一个新列表(因此地址会发生变化),新列表是原列表中元素的重复。

>>> aList=[3,5,7]
>>> bList=aList
>>> id(aList)
2169108124744
>>> id(bList)
2169108124744
>>>
>>> aList=aList*3
>>> aList
[3, 5, 7, 3, 5, 7, 3, 5, 7]
>>> id(aList)
2169109384072
>>> id(bList)
2169108124744

当使用*运算符将包含列表的列表重复并创建新列表时,并不创建元素的复制,而是创建已有对象的引用。因此,当修改其中一个值时,相应的引用也会被修改。

>>> x=[[None]*2]*3
>>> x
[[None, None], [None, None], [None, None]]
>>> id(x)
2169110117192
>>> x[0][0]=5
>>> x
[[5, None], [5, None], [5, None]]
>>> id(x)
2169110117192

1.1.3列表元素的删除
(1)使用del命令删除列表中指定位置上的元素。

>>> aList=[1,2,3]
>>> del aList[1]
>>> aList
[1, 3]

(2)使用列表的pop()方法删除并返回指定(默认为最后一个)位置上的元素,如果给定的索引超出了列表的范围则抛出异常。

>>> aList=[1,2,3,4]
>>> aList.pop()
4
>>> aList
[1, 2, 3]
>>> aList.pop(1)
2
>>> aList
[1, 3]

(3)使用列表对象的remove()方法删除首次出现的指定元素(重复出现的元素的第一次出现的),如果列表中不存在要删除的元素,则抛出异常。

>>> aList=list((9,1,9,0,8,6,7,1,6))
>>> aList.remove(9)
>>> aList
[1, 9, 0, 8, 6, 7, 1, 6]

代码编写好后必须经过反复的测试,不能满足于几次测试结果正确。例如,下面的代码成功地删除了列表中的重复元素,执行结果是完全正确的。

>>> import time
>>>
>>> start=time.time()
>>> x=[9,1,9,0,8,6,7,1,6]
>>> for i in x:
...     if i == 1:
...             x.remove(i)
...
>>> print(x,time.time()-start)
[9, 9, 0, 8, 6, 7, 6] 75.83392548561096
>>>
>>>
>>> x=[1,2,1,1,1]
>>> for i in x:
...     if i==1:
...             x.remove(1)
...
>>> x
[2, 1]

我们发现问题了,为什么会这样呢?

  • 两组数据的本质区别在于,第一组数据没有连续的“1”,第二组数据中存在连续的“1”.出现这个问题的原因是列表的自动内存管理功能
  • 在del列表元素时,Python会自动对列表内存进行收缩并移动列表元素以保证所有元素之间没有空隙,增加列表元素时也会自动扩展内存并对元素进行移动以保证元素之间没有空隙。每当插入或删除一个元素之后,该元素位置后面所有元素的索引就都改变了。

正确的做法:

>>> x=[1,2,1,2,1,1,1]
>>> for i in x[::]:     #切片
...     if i == 1:
...             x.remove(i)
...
>>> x
[2, 2]

或者

>>> x=[1,2,1,2,1,1,1]
>>> for i in range(len(x)-1,-1,-1):
...     if x[i]==1:
...             del x[i]
...
>>> x
[2, 2]

1.1.4列表元素访问与计数
(1)使用列表对象的index()方法获取指定元素首次出现的下标,若列表对象中不存在指定元素,则抛出异常。

>>> aList=[3,4,5,5,5,7,9,11,13,15,17]
>>> aList
[3, 4, 5, 5, 5, 7, 9, 11, 13, 15, 17]
>>> aList.index(7)
5
>>> aList.index(5)
2
>>> aList.index(100)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: 100 is not in list

(2)使用列表对象的count()方法统计指定元素在列表对象中出现的次数。

>>> aList
[3, 4, 5, 5, 5, 7, 9, 11, 13, 15, 17]
>>> aList.count(5)
3
>>> aList.count(13)
1
>>> aList.count(2)
0

1.1.5成员资格判断
如果需要判断列表中是否存在指定的值,可以使用count()方法,如果存在则返回大于0的数,如果返回0则表示不存在。否则,使用更加简洁的“in”关键字来判断一个值是否存在于列表中,返回结果为“True”或“False”。

>>> aList
[3, 4, 5, 5, 5, 7, 9, 11, 13, 15, 17]
>>> 3 in aList
True
>>> 20 in aList
False
>>> bList=[[1],[2],[3]]
>>> bList
[[1], [2], [3]]
>>> 2 in bList
False

1.1.6切片操作

  • 切片是Python序列的重要操作之一,适用于列表、元组、字符串、range对象等类型。不能适用于具有惰性求值特点的zip,map,filter等类型。
  • 切片使用2个冒号分隔3个数字来完成,第一个数字表示切片开始位置(默认为0),第二个数字表示切片截至(但不包含)位置(默认为列表长度),第三个数字表示切片的步长(默认为1),当步长省略时可以顺便省略最后一个冒号。可以使用切片来截取列表中的任何部分,得到一个新列表,也可以通过切片来修改和删除列表中部分元素,甚至可以通过切片操作为列表对象增加元素。
  • 切片操作不会因为下标越界而抛出异常,而是简单地在列表尾部截断或者返回一个空列表,代码具有更强的健壮性。
>>> aList=[3,4,5,6,7,8,9,11,13,15,17]
>>> aList
[3, 4, 5, 6, 7, 8, 9, 11, 13, 15, 17]
>>> aList[::]                   #返回包含所有元素的l新列表
[3, 4, 5, 6, 7, 8, 9, 11, 13, 15, 17]
>>> aList[::-1]                 #逆序的所有元素
[17, 15, 13, 11, 9, 8, 7, 6, 5, 4, 3]
>>> aList[::2]                  #偶数位置,隔一个取一个
[3, 5, 7, 9, 13, 17]
>>> aList[1::2]                 #奇数位置,隔一个取一个
[4, 6, 8, 11, 15]
>>> aList[3::]                  #从下标3开始的所有元素
[6, 7, 8, 9, 11, 13, 15, 17]
>>> aList[3:6]
[6, 7, 8]
>>> aList[0:100:1]              #前100个元素,自动截断
[3, 4, 5, 6, 7, 8, 9, 11, 13, 15, 17]
>>> aList[100:]                 #下标100之后的所有元素,自动截断
[]
>>> aList[2]
5
>>> aList[100]
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
IndexError: list index out of range
>>> #直接使用下标访问会发生越界

可以使用切片来原地修改列表内容:

>>> aList=[3,5,7]
>>> aList[len(aList):]=[9]              #在尾部追加元素
>>> aList
[3, 5, 7, 9]
>>> aList[len(aList):]=[1,2]
>>> aList
[3, 5, 7, 9, 1, 2]
>>> aList[:3]=[1,2,3]                   #替换前3个元素
>>> aList
[1, 2, 3, 9, 1, 2]
>>> aList[:3]=[]                        #删除前3个元素
>>> aList
[9, 1, 2]
>>> aList=list(range(10))
>>> aList
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> aList[::2]=[0]*5
>>> aList
[0, 1, 0, 3, 0, 5, 0, 7, 0, 9]
>>> aList[::2]=[1]*3                    #切片不连续,两个元素个数必须一样多
#上一个为替换偶数位置上的元素
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: attempt to assign sequence of size 3 to extended slice of size 5

使用del与切片结合来删除列表元素:

>>> aList
[0, 1, 0, 3, 0, 5, 0, 7, 0, 9]
>>> del aList[::2]
>>> aList
[1, 3, 5, 7, 9]
>>> del aList[:3]
>>> aList
[7, 9]

切片返回的是列表元素的浅复制

>>> aList=[3,5,7]
>>> bList=aList
>>> bList
[3, 5, 7]
>>> bList[1]=8
>>> aList
[3, 8, 7]
>>> aList==bList
True
>>> aList is bList              #两个列表是一个对象
True
>>> id(aList)
1642669954120
>>> id(bList)
1642669954120

所谓浅复制,是指生成一个新的列表,并且把原列表中所有元素的引用都复制到新列表中。如果原列表中只包含整数、实数、复数等基本类型或元组、字符串这样的不可变类型的数据,一般是没有问题的。如果原列表中包含列表之类的可变数据类型,由于浅复制时只是把子列表的引用复制到新列表中,这样的话修改任何一个都会影响另外一个。

>>> aList=[3,4,5]
>>> bList=aList[::]             #切片,浅复制
>>> aList==bList                #两个列表的元素完全一样
True
>>> aList is bList              #但是不是同一个对象
False
>>> id(aList)==id(bList)        #内存地址也不一样
False
>>> bList[1]=8                  #修改其中一个另一个不会受影响
>>> bList
[3, 8, 5]
>>> aList
[3, 4, 5]

1.1.7列表排序
使用列表对象的sort()方法进行原地排序,支持多种不同的排序方法。

>>> aList=[3,4,5,6,7,9,11,13,15,17]
>>> import random               #导入函数
>>> random.shuffle(aList)       #随机打乱列表元素
>>> aList
[5, 7, 15, 4, 13, 3, 9, 6, 11, 17]
>>> aList.sort()                #默认升序排序
>>> aList
[3, 4, 5, 6, 7, 9, 11, 13, 15, 17]
>>> aList.sort(reverse=True)    #降序排序
>>> aList
[17, 15, 13, 11, 9, 7, 6, 5, 4, 3]
>>> aList.sort(key=lambda x:len(str(x)))        #按转换成字符串的长度排序
>>> aList
[9, 7, 6, 5, 4, 3, 17, 15, 13, 11]

使用内置函数sorted()对列表进行排序并返回新列表

>>> aList=[9,7,6,5,4,3,17,15,11,13]
>>> sorted(aList)               #升序排序
[3, 4, 5, 6, 7, 9, 11, 13, 15, 17]
>>> id(aList)
2350518326344
>>> sorted(aList,reverse = True)#降序排序
[17, 15, 13, 11, 9, 7, 6, 5, 4, 3]
>>> id(aList)
2350518326344

使用列表对象的reverse()方法将元素原地逆序

>>> aList
[9, 7, 6, 5, 4, 3, 17, 15, 11, 13]
>>> aList.reverse()
>>> aList
[13, 11, 15, 17, 3, 4, 5, 6, 7, 9]

使用内置函数reversed()对列表元素进行逆序排列并返回迭代对象,具有惰性求值的特点

>>> aList
[13, 11, 15, 17, 3, 4, 5, 6, 7, 9]
>>> newList=reversed(aList)             #返回reversed对象
>>> list(newList)                       #把reversed对象转换成列表
[9, 7, 6, 5, 4, 3, 17, 15, 11, 13]
>>> for i in newList:
...     print(i,end=' ')                #这里没有输出内容
...                                     #迭代对象已遍历结束
...
>>> newList=reversed(aList)             #重新创建revered对象
>>> for i in newList:
...     print(i,end=' ')
...
9 7 6 5 4 3 17 15 11 13 >>>

1.1.8用于序列操作的常用内置函数
len(列表):返回列表中的元素个数,适用于元组,字典,集合,字符串等。
max(列表),min(列表):返回列表中的最大或最小元素,同样书用于元组、字典、集合、range对象等。
sum(列表):对列表的元素进行求和运算,对非数值型列表运算需要指定start参数,同样适用于元组、range。

>>> sum(range(1,11))
55
>>> sum(range(1,11),5)                  #指定startw参数为5,等价于5+sum(range(1,11))
60
>>> sum([[1,2],[3],[4]],[])             #这个操作占用空间较大,慎用
[1, 2, 3, 4]

zip()函数返回可迭代的zip对象。zip对象具有惰性求值特点,就是不支持下标操作,不支持随机访问,只能按顺序访问,即不可以支持切片操作。

>>> aList=[1,2,3]
>>> bList=[4,5,6]
>>> cList=zip(aList,bList)              #返回zip对象
>>> cList
<zip object at 0x00000223479FBEC8>
>>> list(cList)                         #把zip对象转换成列表
[(1, 4), (2, 5), (3, 6)]

enumerate(列表):枚举列表元素,返回枚举对象,其中每个元素为包含下标和值的元组。该函数对元组、字符串同样有效。

>>> for item in enumerate('abcdef'):
...     print(item,end=' ')
...
(0, 'a') (1, 'b') (2, 'c') (3, 'd') (4, 'e') (5, 'f') >>>
posted @ 2019-08-05 13:50  旅人_Eric  阅读(265)  评论(0编辑  收藏  举报