提前声明:本文学习笔记
原文英文内容为:http://nvie.com/posts/iterators-vs-generators/
Containers 容器
容器是一个装在元素的数据结构,他支持各种测试。这些数据结构活动于内存中,大多数情况下数据也会存储在内存中。
在 Python 中,有一些熟知的例子:
- list
- set
- dict
- tuple
- str
这些数据结构都有自己的子集,具体就不在这里过过多的叙述。
Containers 很容易掌握,因为你可以完全把它们当作现实生活中的容器,比如一个盒子,一座房子,或者一艘船等等
严格意义上来说,如果一个对象可以是否能容下真实的元素,那么他就可以被称为容器。可以以lists,sets,tuples 作为测试的执行对象。
Iterables 可迭代对象
简单的说,大部分容器都是可迭代对象。
但是还有很多内容也是可迭代的,比如说打开文件,连接套接字等等。
Container 大多是有限的,但是可迭代对象或许代表了无限的数据源。
iterable 是一个对象,而不仅仅是一个数据结构,他可以返回一个迭代器(用于返回所有的元素)。这样表述有点疑惑,但是iterable和iterator有着非常重要的区别。
提示:
通常来说,出于务实的原因,iterable 类在类中实现两个方法: iter() 和 next(),并且返回 iter() 给 self,这使得大多数的 iterable 类既是 itreable 也有 迭代器iterator。
Iterator 迭代器
所以,那什么才是迭代器 iterator 呢。
只要能通过调用 next() 提取下一个值就是可以,任何一个对象,如果包含了 next() 方法,那么它就是一个迭代器。至于如何获取下一个值无关紧要,这一点就保障了它是一个迭代器。
迭代器可以理解为 取值的工厂类,每次需要取值只需要调用 the next 即可,它知道如何计算因为iterator控制者内部状态。
迭代器的例子非常多,所有的 itertools 中的方法返回的都是迭代器,有些迭代器会产生无限的序列,有些产生有限长度的序列。
Generator 生成器
最后一个,生成器,generator 是一个特殊的迭代器——更优雅的迭代器
生成器也可以编写一个迭代器类似于 斐波那契数列,它以一种优雅且简易的语法来避免在类中写 iter() 和 next() 方法。
def fib():
prev, curr = 0, 1
while True:
yield curr
prev, curr = curr, prev + curr
f = fib()
list(islice(f, 0, 10))
>>> [1, 1, 2, 3, 5, 8, 13, 21, 34, 55]
是不是很优雅,注意到一个非常有魔力的关键字没,它对于美化代码起了关键性的作用
yield
让我们层层分析其中的奥秘:
首先,记住 fib 方法只是一个普通的 Python 方法,没什么特殊性。
但是值得注意的是,在整个方法体中并没有 return (返回)任何关键字。这个方法返回的值被作为generator(一个iterator,一个工厂类,控制状态的对象)
然后,当 f = fib() 被调用,生成器 generator(一个工厂)会被实例化并且返回。此时不会有任何代码被执行:因为generator是一个懒加载类型的初始化状态。更有趣的是,在 prev, curr = 0, 1
这一行,代码仍然没有被执行。
接下来,generator 实例被 islice() 方法封装,这个方法也是一个 iterator,采用的也是懒加载初始化方法。同样,这里也什么都没有发生。
同样的,迭代器 iterator 被list() 方法包裹,此时回消费所有的参数并且构建一个list。为了这样做,代码才考试调用 islice()实例 中的 next() 方法,再进一步开始调用 f 实例中的 next() 方法。
但是每一次只能执行一步,在第一次调用的时候,代码回执行 prev, curr = 0, 1
,然后进入 while Ture
循环,然后遇到了 yield curr
语句,它将会生成一个值,与当前 curr 的内容相同,并且再次进入睡眠状态。
此时 generator 生成的值回传送到 islice() 的保重中,它同样会生成一个value (因为现在还没有传送满10个值),接下来list就可以添加 value 1 到list中。
接下来,代码会请求 islice() 获取下一个值,递进请求 f 下一个值,此时会将f从上一个状态‘取消暂停’(唤醒了f实例) ,继续执行下一条语句prev, curr = curr, prev + curr
。然后会重新下一个迭代进入 while 循环,继续击中 yield curr
语句,返回下一个 curr 值。
以上的步骤将一直发生直到输出了10个元素给list,这时候 list() 会请求 islice() 获取第11个值,islice() 会抛出 StopIteration 异常,表明获取的元素应到底了,list会返回最终的结果:十个元素组成的list,前十个 Fibonacci 数字。
这里提示一下,generator 并没有接受到第十一个 next() 调用,实际上,它再也不会被使用了,稍后会被垃圾回收器处理。
生成器的类型
在Python中有两种类型的生成器 generator:generator functions 和 generator expressions。
generator function 是指任何一个包含 yield 关键字的方法,上面一个例子属于此类。
另一种类型的生成器是一个list语法,类似于generator,它的语法十分优雅,而且使用极少的代码来实现。
numbers = [1, 2, 3, 4, 5, 6]
[x * x for x in numbers]
{x: x * x for x in numbers}
lazy_squares = (x * x for x in numbers)
print(next(lazy_squares))
print(next(lazy_squares))
print(next(lazy_squares))
list(lazy_squares)
总结
generators 是一个实用性非常强的编程结构,它允许你用更少的中间过程来快速编码,另外,它能高效地利用CPU 和内存。它趋向于让你使用更少行的代码。