#第二部分 数据结构 你可能已经注意到,有几个提到的操作对文本,列表和表格产生的效果相同。文本,列表和表格一起被称为队列。[...] for命令一般在队列上也适用
— Geurts, Meertens and Pemberton ABC程序员手册
在创造python之前,Guido曾是一个ABC语言-一个10年的为新手创建一个编程环境的研究项目-的创造贡献者。ABC引入了许多想法,我们现在称之为的“Pythonic”:序列的通用操作,内置的元组和映射类型,结构由缩进产生,强类型没有变量声明等。这不是偶然的,Python是用户友好的。Python从ABC序列继承统一处理。字符串,列表,字节序列,数组,XML元素和数据库结果共享一组丰富的常用操作:包括迭代、切片、排序和级联。
了解各种可用的Python序列中可以从重新发明轮子中拯救我们,同时它们通用的接口激励我们创造API适当支持,并充分利用现有和将来的序列类型。 本章的大部分讨论适用于包括从熟悉的列表到Python3中的新的字串和字节类型在内的一般序列。
具体的关于列表、元组、数组和队列的话题也包括在这里,但对Unicode字符串和字节序列的介绍是延后至第4章。此外,这里的想法是介绍已能够使用的序列类型。创建你自己的序列类型是第10章的主题。
##内置序列概述 标准库提供了用C语言实现的序列类型的丰富的选择: 容器序列 列表,元组和collections.deque可以容纳不同类型的项目。 扁平序列 str, bytes, bytearray, memoryview和array.array只能容纳相同类型的项目。 容器序列保持引用它们包含的对象的引用,这可能是任何类型的,而扁平的序列是将每个项目的值物理存储在其自己的内存空间中的,而不是作为不同的对象。因此,平面序列更为紧凑,但它们仅限于保持原始值,如字符、字节和数字。
另一种方式分组序列类型的方法是可变性: 可变序列 列表,字节数组,array.array,collections.deque和memoryview 不可变序列 元组,str和bytes
图2-1 从collections.abc一些类绘制的UML类图。超类都在左侧;继承箭头从子类指向到超类。斜体名称是抽象类和抽象方法
图2-1有助于形象化可变序列和那些不可变序列的不同,可变序列同时还继承了它们的几种方法。需要注意的是内置的具体序列类型实际不是Sequence和MutableSequence ABCs(抽象基类) 中所描绘的子类,但抽象基类仍然是作为从一个形式化的序列类型期待一个全特性的序列类型功能有用的。 牢记这些共同的特点 - 可变VS不变;容器VS扁平 - 有助于你推断一个序列类型其他序列类型不同在哪里。
最根本的序列类型是列表 -分可变和混合型。 我相信你是轻松的处理它们,所以我们会跳转到列表推导式,一种强大的方式构建列表因为语法可能不熟悉没有充分利用。
掌握列表解析打开生成器表达式的大门- 生成器表达式其他用途之间 - 能够产生元素来填充任何类型的序列。 这两个都是下一个部分的主题。
##列表解析和生成器表达式 一个快速的方法来建立一个序列是使用一个列表的解析(如果目标是一个列表)或一个生成器表达式(所有其他类型的序列)。如果你不是每天都在使用这些语法形式,我打赌你错过了写更具可读性,并在同一时间往往更快代码的机会。
如果你怀疑我的说法,这些结构是“更可读的”,请继续阅读。我会试图说服你。
tips:为简洁起见,许多Python程序员称列表推导式为listcomps,称生成器表达式为genexps。我也会用这些词
###列表解析和可读性 这是一个测试:你觉得例2-1和例2-2哪一个更容易阅读
示例2-1 用一个字符串的Unicode码建立一个列表
>>> symbols = '$¢£¥€¤'
>>> codes = []
>>> for symbol in symbols:
... codes.append(ord(symbol)) ...
>>> codes
[36, 162, 163, 165, 8364, 164]
示例2-2 用一个字符串的Unicode码建立一个列表程序2
>>> symbols = '$¢£¥€¤'
>>> codes = [ord(symbol) for symbol in symbols]
>>> codes
[36, 162, 163, 165, 8364, 164]
知道一点点的Python知识可以阅读例子2-1。然而,在学习listcomps我发现例2-2的可读性更好,因为它的意图是明确的。
一个循环可以用来做很多不同的事情:扫描一个序列来计数或选择其中的项,计算总量(金额,平均数),或处理任何其他数量。在例子2-1中该代码是建立一个列表。2-2相反,一个listcomp意味着只做一件事:建立一个新的列表。
当然,有可能滥用列表推导式写真的难以理解的代码。我见过使用listcomps的Python代码只是用重复的代码块来为其副作用埋单。如果你不做与产生的列表有关的事情,请不要使用该语法。此外,尽量保持简短。如果列表解析超过两行以上,最好的打破它或重写为一个普通的旧的循环。使用您的最佳判断:Python作为英语,对明确的写作没有硬性规定。
tips:在Python代码中,[],{}或()中对换行是忽略的。所以你可以不用丑陋的\行建立多行列表,listcomps,genexps,词典等。
####listcomps不再泄漏自己的变量
看下面的Python 2.7的控制台会话:
Python 2.7.6 (default, Mar 22 2014, 22:59:38)
[GCC 4.8.2] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> x = 'my precious'
>>> dummy = [x for x in 'ABC']
>>> x
'C'
你可以看到,x的初始值被截断。这不再发生在Python 3。
Python 3:
>>> x = 'ABC'
>>> dummy = [ord(x) for x in x]
>>> x
'ABC'
>>> dummy [65, 66, 67]
>>>
###listcomps对比map和filter
listcomp做的所有map和filter功能也能在不扭曲Python lambda表达式功能不改变的情况下完成。考虑例子2-3。
例子2-3 通过listcomp和map/filter进行同样的列表构建
>>> symbols = '$¢£¥€¤'
>>> beyond_ascii = [ord(s) for s in symbols if ord(s) > 127]
>>> beyond_ascii
[162, 163, 165, 8364, 164]
>>> beyond_ascii = list(filter(lambda c: c > 127, map(ord, symbols)))
>>> beyond_ascii
[162, 163, 165, 8364, 164]
我过去认为,map和filter比等效的listcomps更快,但Alex Martelli指出,并非如此,至少在以上的例子不是这样。
在第5章中,我将有更多的关于map和filter的说明。现在我们用listcomps计算笛卡尔积:从两个或两个以上列出的所有项中建立一个包含元组的列表。 ###笛卡尔积 listcomps可以从两个或两个以上的可迭代对象的笛卡尔积生成列表。组成的笛卡尔积的项是由每一个输入的元组元素构成。结果列表有一个长度相等于乘的输入可迭代对象的长度。见图2-2
例如,想象你需要制作一个两种颜色和三种尺寸的T恤衫列表。例2-4显示了如何使用listcomp生成列表。结果有6个项目。
例2-4 使用列表解析的笛卡尔乘积。
>>> colors = ['black', 'white']
>>> sizes = ['S', 'M', 'L']
>>> tshirts = [(color, size) for color in colors for size in sizes]
>>> tshirts
[('black', 'S'), ('black', 'M'), ('black', 'L'), ('white', 'S'),
('white', 'M'), ('white', 'L')] >>> for color in colors:
... for size in sizes:
... print((color, size)) ...
('black', 'S')
('black', 'M')
('black', 'L')
('white', 'S')
('white', 'M')
('white', 'L')
>>> tshirts = [(color, size) for size in sizes ... for color in colors]
>>> tshirts
[('black', 'S'), ('white', 'S'), ('black', 'M'), ('white', 'M'),
('black', 'L'), ('white', 'L')]