字典——迭代和优化
如果上一节的for
看起来像之前介绍过的列表理解表达式,这是应该的:它们两个都是真正的通用迭代工具。事实上,它们两个都适用于任何遵循迭代协议(在Python中所有迭代工具下的无处不在的理念)的可迭代对象。
简要地说,如果对象是在内存中的物理存储序列,或是在迭代操作(一种“虚拟”序列)上下文中每次生成一项的对象,那它就是可迭代的。更规范地,两种对象都被认为是可迭代的,因为它们支持迭代协议——它们以一个在响应next
调用而向前推进的对象来响应iter
调用,并当生成所有值后引发异常。
之前看到的generator理解表达式就是这种对象:它的值并不一次性存储在内存中,但按请求产生(通常是被迭代工具)。当Python文件对象被一个迭代工具使用时,是类似地逐行迭代:文件内容不是列表,它是按需获取的。在Python中,这两种都是可迭代对象——一个3.X中扩展的种类,来引入像range
和map
这样的核心工具。
本书后面对迭代协议有更多论述。暂时记住每个从左到右扫描对象的Python工具都使用迭代协议。这是为什么在之前章节中使用的sorted
调用直接适用于字典的原因——不必调用keys
方法来获得一个序列,因为字典是可迭代对象,带有一个next
返回连续的键。
它可能还帮助你认识到任何列表理解表达式(比如这个,它计算了一个数字列表的平方):
>>> squares = [x ** 2 for x in [1, 2, 3, 4, 5]]
>>> squares
[1, 4, 9, 16, 25]
总能被编码为for
循环的等价物,在这个过程中用手动追加的方式构建起结果列表:
>>> squares = []
>>> for x in [1, 2, 3, 4, 5]: # 这是列表理解做的事情
squares.append(x ** 2) # 内部都运行迭代协议
>>> squares
[1, 4, 9, 16, 25]
两个工具都在内部利用了迭代工具并产生了同样结果。然而,列表理解和相关的函数式编程工具如map
和 filter
目前在一些类型的代码上都经常运行得比 for
循环更快(甚至可能快两倍)——对大数据集,这个属性在你的程序中可能很重要。然而,话虽如此,应该指出性能测量在Python中是很棘手的任务,因为它自身优化了很多,而且它们随版本不同而不同。
在Python中的一个经验法则是首先简单可靠地编码,在程序工作起来后,在已证明了有真正的性能问题后,再去担心性能。通常,代码会足够快而无需修改。然而,如果确实需要为性能而微调代码,Python包括了帮助你的工具,包括time
和timeit
模块用来测量替代方案的速度,还有profile
模块用于隔离瓶颈。
本书后面和Python手册中有关于这些的更多知识(特别是见第21章的评分案例研究)。因为这只是预览,让我们前进到下一个核心数据类型。