文章导读:
1. 了解python中的序列:列表(list)和元组(tuple)
2. 了解序列的构建:列表推导和生成器
3. 了解序列的操作:切片、拼接、排序
内置序列类型分类
Python有丰富的序列类型,大致可按两种标准分类
- 按能否存放不同类型的数据
- 容器序列(可以存放) : list,tuple,collections.deque
- 扁平序列(不可以) : str,bytes,bytearray,memoryview,array.array
- 注意:容器序列中存放的是包含的对象的引用,而扁平序列中存放的是值不是引用
- 按能否被修改
- 可变序列(MutableSequence) :list,bytearray,array.array,collections.deque,memoryview
- 不可变序列 :tuple,str,bytes
构建序列:列表推导和生成器表达式
对于序列构建,有过编程基础的可能比较熟悉生成器表达式。例如:
tuple(i for i in range(4))
array.array('I',(i for i in range(4)))
但是在Python中,提供了一种更为简介的方式,就是列表推导。
列表推导的形式如下:
symbols = '$¥&@'
codes = [ord(symbol) for symbol in symbols] #把字符串转化为Unicode
列表推导的作用只有一个:生成列表。它的优势在于可读性高,因此要保持简短,避免滥用列表推导。如果列表推导的语句过长,应该考虑使用for循环重写。
列表推导还可以用于生成两个或以上可迭代类型的笛卡尔积。
colors = ['red','blue','green']
sizes = ['S','M','L']
tshirts = [(color,size) for color in colors for size in sizes]
元组:不仅仅是不可变列表
刚开始学习Python时,对元组的概念可能仅仅停留在“不可变列表”。但其实元组有两种公用:记录和不可变列表。
我们首先谈谈元组的记录功能,元组用于没有字段名的记录,元组中的每个元素都存放了记录中一个字段的数据,外加这个字段的位置。
元组拆包
而数据的价值在于使用,因此有了元组拆包,拆包让元组完美的被当作记录来使用。最简单易懂的拆包形式就是平行赋值,也就是把一个可迭代对象里的元素一并赋值到对应的变量中。例如:
info = (33.9425,-118.408056)
latitude,longtitude = info #元组拆包
?思考1:拆包时,不是需要所有数据,那怎么办?
占位符和*很好的处理了这种情况,占位符用于排除无关信息,而*用来处理剩下的元素,其实在很多编程语言中, 都有类似*args来获取不确定数量的参数。
import os
_,filename = os.path.split('/home/hzy/test.txt') #filename:test.txt
## 注意:python3.5一下不支持*args表达式
a,b,*rest = range(5) #rest:[2,3,4]
a,*body,c,d = range(5) #rest:[1,2]
元组拆包是支持嵌套式的,例如像(a,b,(c,d))形式,只需对应的类型正确。
具名元组
?思考2:元组作为记录来说,似乎设计的很好了,但还是少了一个功能:给记录中字段命名。
collections.namedtuple函数解决了这一问题,它可以构建一个带字段名的元组和一个有名字的类。
from collections import namedtuple
City = namedtuple('City','name country population coordinates')
tokyo = City('Tokyo','Japan','36.933',(35.689722,139.691667))
具名元组有一些自己专有的属性。常有的有:
1. _fields属性:包含类所有字段名
2. _make():接受一个可迭代对象生成这个类的一个实例
3. _asdict():把具名元组以collections.OrderedDict的形式返回
作为不可变列表的元组
我们再来谈元组的不可变性,我们可以根据下表了解元组和列表的区别
TODO
?思考3:上表中,为什么元组具有拼接功能,而不具有就地拼接功能。
切片
在Python中,list,tuple,str等序列都支持切片。切片的形式为s[start:stop:step],step可以为负值,负值意味着反向取值。而实际切片时,是调用了seq.__getitem__(slice(start,stop,step))特殊方法.
?思考4:为什么切片和区间会忽略最后一个元素
这是因为在Python和其他语言中,都是以0作为起始下标。
python还支持多维切片,在后面的4.2的numpy.ndarray会提到。
python也支持给切片赋值,例如:
l = list(range(10)) #l [0,1,2,3,4,5,6,7,8,9]
l[2:5] = [20,30] #l [0,1,20,30,5,6,7,8,9]
del l[5:7] #l[0,1,20,30,5,8,9]
l[3::2] = [11,22] #l[0,1,20,11,5,22,9]
拼接
Python序列支持+和操作,注意,* 这两种拼接都会构建一个新的序列。 **
TODO
Python也支持增量赋值+=和*=。
+=实际上调用的特殊方法是__iadd__(就地加法),但如果序列没有实现该方法,会退一步调用__add__。在Python中,如果一个方法对对象进行的是就地改动,那它就会返回None,好让调用者知道传入的参数发生改变,但并未产生新的对象。
*=也同于+=,调用的是__imul__。
基于上诉原因,对于可变序列来说,增量赋值不会产生新的对象;而对于不可变序列来说,因为不支持就地改动,因此使用增量赋值时实际调用的是如__add__,会产生新的对象。这也解释了思考3。
排序
list.sort为就地排序,与上文提到的就地改变契合,因此不会生成新的列表。
而内置函数sorted,会新建一个列表作为返回值。
不管是list.sort,还是sorted。都有两个可选关键字参数。
1. reverse:是否降序输出。默认为False。
2. key:根据关键字改变排序方式。如key=len,根据长度排序。key=str.lower,忽略大小写。