你可能注意到了,之前提到的几个操作可以无差别地应用于文本、列表和表格上。
我们把文本、列表和表格叫作数据火车……FOR 命令通常能作用于数据火车上。
——Geurts、Meertens 和 Pemberton
ABC Programmer’s Handbook
内置序列类型概览
- 容器序列
list
、tuple
和collections.deque
这些序列能存放不同类型的数据。 - 扁平序列
str
、bytes
、bytearray
、memoryview
和array.array
,这类序列只能容纳一种类型。
容器序列存放的是它们所包含的任意类型的对象的引用,而扁平序列里存放的是值而不是引用。换句话说,扁平序列其实是一段连续的内存空间。由此可见扁平序列其实更加紧凑,但是它里面只能存放诸如字符、字节和数值这种基础类型。
序列类型还能按照能否被修改来分类。
- 可变序列
list
、bytearray
、array.array
、collections.deque
和memoryview
。 - 不可变序列
tuple
、str
和bytes
列表推导和生成器表达式
列表推导和可读性
列表推导是构建列表(list)的快捷方式,生成器表达式用来穿件其他任何类型的序列。
1 | # 比较两段代码 |
列表推导能够提升可读性。
只用列表推导来创建新的列表,并尽量保持简短(不要超过一行)
列表推导同 filter 和 map 的比较
1 | symbols = 'abcde' |
[101]
[101]
笛卡尔积
1 | colors = ['black', 'white'] |
[('black', 'S'), ('black', 'M'), ('black', 'L'), ('white', 'S'), ('white', 'M'), ('white', 'L')]
[('black', 'S'), ('white', 'S'), ('black', 'M'), ('white', 'M'), ('black', 'L'), ('white', 'L')]
生成器表达式
列表推导与生成器表达式的区别:
- 生成器表达式遵守实现了迭代器接口,可以逐个地产出元素。列表推导是先建立一个完整的列表,再将这个列表传递到构造函数里。
- 语法上近似,方括号换成圆括号
1 | # symbols = 'abcde' |
- 如果生成器表达式是一个函数调用过程中的唯一参数,则不需要额外括号
- 生成器会在 for-loop 运行时才生成一个组合。逐个产出元素
1 | colors = ['black', 'white'] |
black S
black M
black L
white S
white M
white L
元祖不仅仅是不可变的列表
元祖与记录
- 元祖是对数据的记录
- 元祖的位置信息为数据赋予了意义。对元祖内元素排序,位置信息将丢失
1 | # LA 国际机场经纬度 |
BBACE342567
ESPXDA205856
USA31195855
USA
BBA
ESP
元祖拆包
- 平行赋值
1 | lax_coordinates = (33.9425, -118.408056) |
33.9425
-118.408056
- 交换变量值,不使用中间变量
1 | a = 3 |
4
3
*
运算符,把一个可迭代对象拆开作为函数参数
1 | divmod(20, 8) |
2
4
- 函数用元祖形式返回多个值
_ 用作占位符,可以用来处理不需要的数据
1 | import os |
id_rsa.pub
- 用
*
处理省下的元素
1 | a, b, *rest = range(5) |
0 1 [2, 3, 4]
0 1 [2]
0 1 []
0 [1, 2] 3 4
[0, 1] 2 3 4
嵌套元祖拆包
1 | metro_areas = [ |
| lat. | long.
Mexico City | 19.4333 | -99.1333
New York-Newark | 40.8086 | -74.0204
Sao Paulo | -23.5478 | -46.6358
将元祖作为记录仍缺少一个功能:字段命名
具名元祖(numedtuple)
collections.namedtuple
是一个工厂函数,用来构建带字段名的元祖和一个有名字的
namedtuple 构建的类的实例所消耗的内存和元祖是一样的,因为字段名都存在对应的类里。
实例和普通的对象实例小一点,因为 Python 不会用__dict__
存放实例的属性
1 | from collections import namedtuple |
City(name='Tokyo', country='JP', population=36.933, coordinates=(35.689722, 129.691667))
36.933
(35.689722, 129.691667)
namedtuple
除了从普通元祖继承的属性外,还有一些专有属性。
常用的有:
_fields
类属性_make(iterable)
类方法_asdict()
实例方法
1 | print(City._fields) |
('name', 'country', 'population', 'coordinates')
OrderedDict([('name', 'Delhi NCR'), ('country', 'IN'), ('population', 21.935), ('coordinates', LatLong(lat=28.613889, long=77.208889))])
name: Delhi NCR
country: IN
population: 21.935
coordinates: LatLong(lat=28.613889, long=77.208889)
作为不可变列表的元祖
对比列表和元祖的方法
// 插入表格
结论:
- 除了增减元素相关的方法和
__reversed__
外,元祖支持列表的其他所有方法。
切片
在 Python 里, 列表(list),元祖(tuple)和字符串(str)这类序列类型都支持切片操作
为什么切片的区间会忽略最后一个元素
- Python 以0 作为起始下标
- 当只有后一个位置信息时,可以快速导出切片和区间的元素数量
- 当起止位置信息课件是,可以快速计算出切片和区间的长度 (stop - start)
- 可利用任意一个下标把序列分割成不重叠的两部分。
my_list[:x]
my_list[x:]
1 | ### 对对象进行切片 |
1 | s = 'bicycle' |
bye
elcycib
eccb
多维切片和省略
[]
运算符可以使用以逗号分开的多个索引或切片。
如 a[i, j]
,a[m:n, k:1]
得到二维切片
要正确处理[]
运算符,对象的特殊方法 __getitem__
,__setitem__
需要以元祖的形式来接受 a[i, j]
的索引。
给切片赋值
切片放在赋值语句左边,或作为 del 操作对象,可以对序列进行嫁接、切除或就地修改
1 | l = list(range(10)) |
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
[0, 1, 20, 30, 5, 6, 7, 8, 9]
[0, 1, 20, 30, 5, 8, 9]
[0, 1, 20, 11, 5, 22, 9]
对序列使用 + 和 *
+
和*
不修改原有的操作对象,而是构建一个新的序列
1 | l = [1, 2, 3] |
[1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3]
abcdabcdabcdabcdabcd
建立由列表组成的列表
a * n,如果在序列 a 中存在对其他可变变量的引用的话,得到的序列中包含的是 n 个对指向同一地址的引用
1 | board = [['_'] * 3 for i in range(3)] |
[['_', '_', '_'], ['_', '_', '_'], ['_', '_', '_']]
[['_', '_', '_'], ['_', '_', 'X'], ['_', '_', '_']]
[['_', '_', 'O'], ['_', '_', 'O'], ['_', '_', 'O']]
序列的增量赋值 +=、*=
+=
背后的特殊方法是__iadd__
方法,没有则退一步调用__add__
- 同理
*=
的特殊方法是__imul__
1 | l = [1, 2, 3] |
4534358344
[1, 2, 3, 1, 2, 3]
4534358344
4536971408
(1, 2, 3, 1, 2, 3)
4546754024
list.sort方法和内置函数sorted
- list.sort 会就地排序列表,方法返回值为 None
- sorted 会新建一个列表作为返回值
- 两个方法都有 reverse 和 key 作为可选的关键字参数
reserve 为 True 时,降序输出。默认为 false
key 只有一个参数的函数,将被用在序列的每一个元素上,其结果作为排序算法依赖的对比关键字
用bisect管理已排序的序列
bisect 模块有两个主要函数:
- bisect
- insort
都利用二分查找法来在有序序列中查找或插入人元素
用 bisect 来搜索
bisect(haystack, needle) 默认为升序,haystack 需要保持有序。
使用方法:
bisect(index, needle) 查找位置 index,再使用 haystack.insert(index, needle) 插入新值
也可以用 insort 来一步到位,且后者速度更快
1 | # BEGIN BISECT_DEMO |
DEMO: bisect_right
haystack -> 1 4 5 6 8 12 15 20 21 23 23 26 29 30
31 @ 14 | | | | | | | | | | | | | |31
30 @ 14 | | | | | | | | | | | | | |30
29 @ 13 | | | | | | | | | | | | |29
23 @ 11 | | | | | | | | | | |23
22 @ 9 | | | | | | | | |22
10 @ 5 | | | | |10
8 @ 5 | | | | |8
5 @ 3 | | |5
2 @ 1 |2
1 @ 1 |1
0 @ 0 0
Array
虽然列表既灵活又简单,但面对各类需求时,我们可能会有更好的选择。比如,要存放 1000 万个浮点数的话,数组(array)的效率要高得多,因为数组在背后存的并不是 float 对象,而是数字的机器翻译,也就是字节表述。这一点就跟 C 语言中的数组一样。再比如说,如果需要频繁对序列做先进先出的操作,deque(双端队列)的速度应该会更快。
array.tofile
和 fromfile
可以将数组以二进制格式写入文件,速度要比写入文本文件快很多,文件的体积也小。
另外一个快速序列化数字类型的方法是使用 pickle(https://docs.python.org/3/library/pickle.html)模块。pickle.dump 处理浮点数组的速度几乎跟array.tofile 一样快。不过前者可以处理几乎所有的内置数字类型,包含复数、嵌套集合,甚至用户自定义的类。前提是这些类没有什么特别复杂的实现。
array 具有 type code
来表示数组类型:具体可见 array 文档.
memoryview
memoryview.cast 的概念跟数组模块类似,能用不同的方式读写同一块内存数据,而且内容字节不会随意移动。
1 | import array |
Deque
collections.deque
是比 list
效率更高,且线程安全的双向队列实现。
除了 collections 以外,以下 Python 标准库也有对队列的实现:
- queue.Queue (可用于线程间通信)
- multiprocessing.Queue (可用于进程间通信)
- asyncio.Queue
- heapq