Python也从ABC
那里继承了用统一的风格去处理序列数据这一特点。不管是哪种数据结构,字符串、列表、字节序列、数组、XML元素,抑或是数据库查询结果,它们都共用一套丰富的操作:迭代、切片、排序,还有拼接。
内置序列类型概览 容器序列(存放不同类型的数据)
list
tuple
collections.deque
扁平序列(只能容纳一种类型)
str
bytes
bytearray
memoryview
array.array
容器序列 存放的是它们所包含的任意类型的对象的引用 ,而扁平序列 里存放的是值 而不是引用。换句话说,扁平序列其实是一段连续的内存空间。由此可见扁平序列其实更加紧凑,但是它里面只能存放诸如字符、字节和数值这种基础类型。
序列类型还能按照能否被修改类分类:
可变序列
list
bytearray
array.array
collections.deque
memoryview
不可变序列
上图显示了可变序列(MutableSequence)和不可变序列(Sequence)的差异,同时也能看出前者从后者那里继承了一些方法。
列表推导和生成器表达式 列表推导是构建列表(list)的快捷方式,而生成器表达式则可以用来创建其他任何类型的序列。
列表推导和可读性 把一个字符串变成Unicode码位的列表:
1 2 3 4 5 6 7 symbols = 'kevin' codes = []for symbol in symbols: codes.append(ord(symbol)) print(codes)
把字符串变成Unicode码位的另外一种写法:
1 2 3 4 symbols = 'kevin' codes = [ord(symbol) for symbol in symbols] print(codes)
1 2 3 x = 'ABC' dummy = [ord(x) for x in x] print(dummy)
列表推导可以帮助我们把一个序列或是其他可迭代类型中的元素过滤或是加工,然后再新建一个列表。
列表推导同filter
和map
的比较 用列表推导和map/filter组合来创建同样的表单
1 2 3 4 5 6 symbols = 'shy_kevin' beyond_ascii = [ord(s) for s in symbols if ord(s) > 110 ] print(beyond_ascii) beyond_ascii = list(filter(lambda c:c >110 ,map(ord,symbols))) print(beyond_ascii)
笛卡儿积 使用列表推导计算笛卡儿积
1 2 3 4 5 6 7 8 colors = ['black' ,'white' ] sizes = ['S' ,'M' ,'L' ] tshirts = [(colors,sizes) for color in colors for size in sizes] print(tshirts)for color in colors: for size in sizes: print((color,size))
输出内容:
1 [(['black' , 'white' ], ['S' , 'M' , 'L' ]), (['black' , 'white' ], ['S' , 'M' , 'L' ]), (['black' , 'white' ], ['S' , 'M' , 'L' ]), (['black' , 'white' ], ['S' , 'M' , 'L' ]), (['black' , 'white' ], ['S' , 'M' , 'L' ]), (['black' , 'white' ], ['S' , 'M' , 'L' ])]
列表推导的作用只有一个:生成列表。如果想生成其他类型的序列,生成器表达式就派上了用场。
生成器表达式 生成器表达式背后遵守了迭代器协议,可以逐个地产出元素,而不是先建立一个完整的列表,然后再把这个列表传递到某个构造函数里。
生成器表达式的语法跟列表推导差不多,只不过把方括号 换成圆括号 而已。
1 2 3 symbols = 'shy_kevin' print(tuple(ord(symbol) for symbol in symbols))
元组不仅仅是不可变的列表 元组不仅是“不可变列表”,还可以用于没有字段名的记录。
元组和记录 元组其实是对数据的记录:元组中的每个元素都存放了记录中一个字段的数据,外加这个字段的位置。正是这个位置信息给数据赋予了意义。
把元组用作记录
1 2 3 4 5 6 7 8 9 lax_coordinates = (33.9425 ,-118.408056 ) city,year,pop,chg,area = ('kevin' ,2003 ,3425 ,0.66 ,8014 ) traveler_ids = [('USA' ,'3732' ),('BRA' ,'CE342567' ),('ESP' ,'XDA205856' )]for passport in sorted(traveler_ids): print('%s/%s' %passport)for country,_ in traveler_ids: print(country)
for
循环可以分别提取元组里的元素,也叫作拆包(unpacking
)。因为元组中第二个元素对我们没有什么用,所以它赋值给“_”占位符。
元组拆包 1 2 3 lax_coordinates = (33.9425 ,-118.408056 ) latitude,longitude = lax_coordinates print('{0}::{1}' .format(latitude,longitude))
在Python中,函数用*args
来获取不确定数量的参数
1 2 3 4 5 a,b,*rest = range(5 ) print(a,b,rest) a,b,*rest = range(2 ) print(a,b,rest)
嵌套元组拆包 用嵌套元组来获取经度
1 2 3 4 5 6 7 8 9 10 11 12 metro_areas = [ ('Tokyo' ,'JP' ,36.933 ,(35.689722 ,139.691667 )), ('Delhi NCR' , 'IN' , 21.935 , (28.613889 , 77.208889 )), ('Mexico City' , 'MX' , 20.142 , (19.433333 , -99.133333 )), ('New York-Newark' , 'US' , 20.104 , (40.808611 , -74.020386 )), ('Sao Paulo' , 'BR' , 19.649 , (-23.547778 , -46.635833 )), ] print('{:15} | {:^9} | {:^9}' .format('' , 'lat.' , 'long.' )) fmt = '{:15} | {:9.4f} | {:9.4f}' for name, cc, pop, (latitude, longitude) in metro_areas: if longitude <= 0 : print(fmt.format(name, latitude, longitude))
每个元组内有4个元素,其中最后一个元素是一对坐标。
我们把输入元组的最后一个元素拆包到由变量构成的元组里,这样就获取了坐标。
if longitude <=0
:这个条件判断把输出限制在西半球的城市。
1 2 3 4 5 6 7 metro_areas = [('东' ,'小一' ,(1 ,2 )), ('南' ,'小二' ,(3 ,4 )), ('西' ,'小三' ,(5 ,6 )), ('北' ,'小四' ,(7 ,8 )),]for direction,name,(latitude, longitude) in metro_areas: print("{0},{1},{2}" .format(direction,name,(latitude, longitude)))
具名元组 collections.namedtuple
是一个工厂函数,它可以用来构建一个带字段名的元组和一个有名字的类。
如何用具名元组来记录一个城市的信息:
1 2 3 4 5 6 7 8 9 10 11 from collections import namedtuple City = namedtuple('City' ,'name country population coordinates' ) tokyo = City('Tokyo' ,'JP' ,36.933 ,(35.689722 ,139.691667 )) print(tokyo) print(tokyo.population) print(tokyo.coordinates)
作为不可变列表的元组
切片 为什么切片和区间会忽略最后一个元素
当只有最后一个位置信息时,我们也可以快速看出切片和区间里有几个元素:range(3)
和my_list[:3]
都返回3个元素。
当起止位置信息都可见时,我们可以快速计算出切片和区间的长度,用后一个数减去第一个下标(stop-start)即可。
1 2 3 4 5 6 7 l = [10 ,20 ,30 ,40 ,50 ,60 ] print(l[:2 ]) print(l[2 :]) print(l[:3 ]) print(l[3 :])
对对象进行切片 1 2 3 4 s = 'bicycle' print(s[::3 ]) print(s[::-1 ]) print(s[::-2 ])
纯文本文件形式的收据以一行字符串的形式被解析
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 invoice = """ 0.....6................................40........52...55........ 1909 Pimoroni PiBrella $17.50 3 $52.50 1489 6mm Tactile Switch x20 $4.95 2 $9.90 1510 Panavise Jr. - PV-201 $28.00 1 $28.00 1601 PiTFT Mini Kit 320x240 $34.95 1 $34.95 """ SKU = slice(0 , 6 ) DESCRIPTION = slice(6 , 40 ) UNIT_PRICE = slice(40 , 52 ) QUANTITY = slice(52 , 55 ) ITEM_TOTAL = slice(55 , None ) line_items = invoice.split('\n' )[2 :] print(line_items)for item in line_items: print(item[UNIT_PRICE], item[DESCRIPTION])
给切片赋值 1 2 3 4 l = list(range(10 )) print(l) l[2 :5 ] = [20 ,30 ] print(l)
对序列使用+和* 1 2 3 l = [1 ,2 ,3 ] print(l*5 ) print(5 *'kevin' )
序列的增量赋值 1 2 3 4 5 6 7 8 t = (1 ,2 ,[30 ,40 ]) t[2 ] += [50 ,60 ] print(t)
不要把可变对象放在元组里面。
增量赋值不是一个原子操作。
它虽然抛出了异常,但还是完成了操作。
list.sort
方法和内置函数sorted
list.sort
方法会就地排序列表,也就是说不会把原列表复制一份。:如果一个函数或者方法对对象进行的是就地改动,那它就应该返回None ,好让调用者知道传入的参数发生了变动,而且并未产生新的对象。
用bisect来管理已排序的序列 bisect
模块包含两个主要函数,bisect
和insort
,两个函数都利用二分查找算法来在有序序列中查找或插入元素。
用bisect
来搜索 根据一个分数,找到它所对应的成绩