本章讨论的内容几乎可以应用到所有的序列类型上,从我们熟悉的list,到Python3中的特有的str和bytes。还会提到跟列表、元组、数组及队列有关的话题。深入理解Python的不同序列类型,不但能让我们避免重新发明轮子,它们的API还能帮助我们把自己定义的API设计得跟原生的序列一样,或者是跟未来可能出现的序列类型保持兼容。
内置序列类型概览
序列类型可以按照存放数据的类型分类:
容器序列
- list
- tuple
- collections.deque
扁平序列
- str
- bytes
- bytearray
- memoryview
- array.array
容器序列存放的是它们所包含的任意类型的对象的引用,而扁平序列里存放的是值而不是引用。换句话说,扁平序列其实是一段连续的内存空间。由此可见扁平序列其实更加紧凑,但是它里面只能存放诸如字符、字节和数值这种基础类型。
序列类型还能按照能否被修改类分类:
可变序列
- list
- bytearray
- array.array
- collections.deque
- memoryview
不可变序列
上图显示了可变类型(mutableSequence)和不可变类型(Sequence)的差异,同时可以看出前者从后者那里继承了一些方法。
列表推导
请看示例代码:
>>> i=2
>>> i
2
>>> [i for i in 'abc']
['a', 'b', 'c']
>>> i
c
Python2中,在列表推导中for关键词之后的赋值操作可能会影响列表推导上下文中的同名变量,示例中i的值被取代了,但是这种情况再Python3中是不会出现的。
生成器表达式
生成器表达式遵守了迭代器协议,可以逐个地产出元素,而不是先建立一个完整的列表,然后再把这个列表传递到某个构造函数里。显然使用生成器表达式方法更能够节省内存。
>>> (i for i in 'abc')
at 0x10842c4c0>
生成器表达式的语法跟列表推导差不多,只不过把方括号改成圆括号而已。
元组不仅仅是不可变列表
有些Python入门教程把元组成为"不可变列表",然而这病没有完全概括元组的特点。除了用作不可变的列表,它还可以用于没有字段名的记录。
元组其实是对数据的记录:元组中的每个元素都存放了记录中一个字段的数据,外加这个字段的位置。正是这个位置信息给数据赋予了意义。
如果只把元组理解为不可变的列表,那其他信息(元素总数和它的位置)似乎就变得可有可无。
元组拆包
>>> a, b, c = (1, 2, 3)
>>> a
1
>>> b
2
>>> c
3
元组拆包可有应用到任何可迭代对象上,唯一的硬性要求是,被可迭代对象中的元素数量必须要跟接受这些元素的元组空挡数一致。
命名元组
collections.namedtuple是一个工厂函数,它可有用来构建一个带字段名的元组和一个有名字的类。 示例如下:
>>> Card = namedtuple("Card", ['rank', 'suit'])
>>>
>>> a = Card(1,2)
>>> a.rank
1
切片
在Python里,list、tuple和str这类序列类型都支持切片操作。 切片语法暂不详述。
对序列使用+和*
Python默认序列是支持+和*操作的,两侧的序列由相同类型的数据构成,在拼接的过程中,两个被操作的序列都不会修被修改,Python会新建一个包含同样数据类型的序列来作为拼接的结果。
>>>a = [1, 2]
>>>a * 3
[1, 2, 1, 2, 1, 2]
>>> 2 * 'abc'
'abcabc'
序列的增量赋值
增量赋值运算符+=和*=的表现取决于它们的第一个操作对象。 +=背后的特殊方法是__iadd_。但是如果类没有实现这个方法的话,Python会退一步调用__add__。 示例代码:
>>> a = (1, 2, [3, 4])
>>> a
(1, 2, [3, 4])
>>> a[2] += [6, 7]
Traceback (most recent call last):File "", line 1, in
TypeError: 'tuple' object does not support item assignment
>>> a
(1, 2, [3, 4, 6, 7])
这个示例中,因为tuple不支持对他的元素赋值,所以会抛出TypeError异常。但是由于a[2]是个列表,本身是支持+=操作的,所以a的值变成了(1, 2, [3, 4, 6, 7])。
总结:
- 不要把可变对象放在元组里面。
- 增量赋值不是一个原子操作。我们刚才看到它虽然抛出了异常,但还是完成了操作。
list.sort方法和内置函数sorted
list.sort方法会就地排序列表,也就是说不会把原列表复制一份。
与list.sort相反的内置函数sorted,它会新建一个列表作为返回值。
用bisect来管理已排序的序列
bisect模块包含两个主要函数,bisect和insort,两个函数都利用二分查找算法来在有序序列中查找或插入元素。
- bisect:在有序序列中查找某个元素的插入位置。
- insort:向有序序列中插入新元素,并保持排序。
双向队列
collections.deque类(双向队列)是一个线程安全、可以快速从两端添加或者删除元素的数据类型。