生成器generator

通过列表生成式可以直接创建列表,但是受到内存限制,列表容量肯定是有限的。而且创建一个包含100万个元素的列表,不仅占用大量内存,如果我们仅仅需要访问其中某几个元素,那么绝大多数空间是浪费了。

所以,如果列表是按照某种算法推算出来的,那么我们可以在循环过程中推算出后面的所有元素。这样就不必创建完整的 list了,从而节省了大量的存储空间。在Python中,一边循环一边计算的机制,成为生成器generator。

要创建一个generator有很多种方法。第一种方法很简单,就是把列表生成式[]改成(),就创建了一个generator:

>>>L = [x*x for x in range(10)]
>>>L
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

>>>g = (x*x for x in range(10))
>>>g
<generator object <genexpr> at 0x10e357a00>

那要怎么全部打印出来呢?用内置函数next()

>>> next(g)
0
>>> next(g)
1
>>> next(g)
4
>>> next(g)
9
>>> next(g)
16
>>> next(g)
25
>>> next(g)
36
>>> next(g)
49
>>> next(g)
64
>>> next(g)
81
>>> next(g)
Traceback (most recent call last):File "<stdin>", line 1, in <module>StopIteration

generator保存的是算法,每次调用next(g),就计算出下一个值,知道计算到最后一个元素,没有更多元素时,抛出StopIteration的错误。

当然,上面这种不断调用next(g)实在是太变态了,正确的方法是使用for循环,因为generator也是可迭代对象:

>>> g = (x * x for x in range(10))
>>> for n in g:
...     print(n)
...

斐波拉切数列用列表生成式写不出来,但是用代码写出来很容易:

def fib(max):
    n, a, b = 0, 0, 1
    while n < max:
        print(b)
        a, b = b, a+b
        n = n+1
    return 'Done'

注意赋值语句:

a, b = b, a+b

相当于:

t = (b, a + b) # t是一个tuple
a = t[0]
b = t[1]

但是不必显式的声明临时变量t。

从上面可以看出,列表是按照斐波拉切算法推导出来的,这和生成器generator很相似。

只需要把上面代码print(b)改成yield b.

函数中有关键字yield就和普通的函数不一样了。普通函数流程是顺序执行到结束返回。而含有yield的函数,每次调用next()的时候执行,遇到yield返回,再次执行的时候从上次yield语句处继续执行。

def fib(max):
    n, a, b = 0, 0, 1
    while n < max:
        yield b
        a, b = b, a+b
        n = n+1
        
print(f)
#运行结果为<generator object fib at 0x10fe6fa50>

f = fib(10)
for n in f:
    print n

例子:杨辉三角

迭代器iterator

我们知道,可以用for()循环的有以下几种:

第一种是集合数据类型如list,tuple,dic,set,str等;

第二种是生成器generator,包括带有yield的函数。

这些能使用for循环的对象统称为可迭代对象Iterable

可以使用isinstance()来判断一个对象是否是可迭代对象。

>>>from collections import Iterable
>>>isinstance([], Iterable)
True
>>>isinstance((x*x for x in range(10)), Iterable)
True
>>>isinstance(100, Iterable)
False

而生成器不但可以用于for循环,还可以被next()函数调用并且返回下一个值,直到最后抛出StopIteration错误,表示无法继续返回下一个值了。

可以被next()调用,并且可以不断返回下一个值的对象,被成为迭代器Iterator

可以使用isinstance()判断对象是否是迭代器Iterator

>>>from collections import Iterator
>>>isinstance((x*x for x in range(10)), Iterator)
True
>>>isinstance([], Iterator)
False
>>> isinstance('abc', Iterator)
False
>>> isinstance(iter([]), Iterator)
True

生成器都是迭代器Iterator,但是可迭代对象Iterable不一定都是生成器Iterator,比如listdictstr都不是迭代器Iterator。把listdictstrIterable变成迭代器Iterator可以用内置函数iter()

为什么listdictstrIterable内置数据类型不是迭代器Iterator?因为Python的迭代器Iterator表示的是一个数据流。Iterator对象可以被next函数调用,并不断返回下一个数据,并直到没有数据时候抛出StopIteration错误。可以把这个数据流看作有序序列,但是我们却不能提前知道序列的长度,只能通过next()函数计算出下一个数据,这种Iterator计算是惰性的,只有在需要返回下一个数据时它才会计算。

Iterator甚至可以表示一个无限长度的数据流,例如全体自然数。而使用list是永远不可能存储全体自然数的。


小结:

可以用for循环的都是可迭代对象Iterable

generatorlistdictstr等对象都可以用for循环,即可迭代对象Iterable

数据集合listdictstr等是循环的都是可迭代对象Iterable;,但不是迭代器Iterator