引入生成器之前我们先来了解一下python函数工作机制:

我们调用一个普通的 Python 函数时,一般是从函数的第一行代码开始执行,结束于 return 语句、异常或者函数结束(可以看作隐式的返回 None)。一旦函数将控制权交还给调用者,就意味着全部结束。函数中做的所有工作以及保存在局部变量中的数据都将丢失。再次调用这个函数时,一切都将从头创建。

对于在计算机编程中所讨论的函数,这是很标准的流程。这样的函数只能返回一个值,不过,有时可以创建能产生一个序列的函数还是有帮助的。要做到这一点,这种函数需要能够“保存自己的工作”。

Python中,这种一边循环一边计算的机制,称为生成器:generator

创建一个generator方法,跟创建一个列表生成式类似,如下:

gen =(x/2 for x in range(5))
type(gen) #generator
print(gen)#<generator object <genexpr> at 0x1096199a8>

如上:即是一个生成器,如果获取generator的元素呢?

如果一个一个的获取值,可以通过next()函数获取

next(gen) #0.0
next(gen) #0.5
next(gen) #1.0
...
next(gen) #2.0
next(gen)
#触发异常

当获取到最后一个后,在调用next(),就会出现 StopIteration 异常

也可以通过for 循环出来,因为generator 是一个可迭代对象(Iterable),如下:

g =(x for x in[3,4,5,6])
for i in g:
  print(i)
# 3 4 5 6

走进生成器:

我们可以在函数中使用yield 使得函数变为生成器,一个生成器函数的定义很像一个普通的函数,除了当它要生成一个值的时候,使用yield 关键字而不是return。如果一个 def 的主体包含yield,这个函数会自动变成一个生成器(即使它包含一个 return)。

生成器函数返回生成器的迭代器。这可能是你最后一次见到“生成器的迭代器”这个术语了, 因为它们通常就被称作“生成器”。要注意的是生成器就是一类特殊的迭代器。作为一个迭代器,生成器必须要定义一些方法(method),其中一个就是__next__()。如同迭代器一样,我们可以使用 next() 函数来获取下一个值。

为了从生成器获取下一个值,我们使用 next() 函数,就像对付迭代器一样(next() 会操心如何调用生成器的 __next__()方法,不用你操心)。既然生成器是一个迭代器,它可以被用在 for 循环中。

每当生成器被调用的时候,它会返回一个值给调用者。在生成器内部使用 yield 来完成这个动作(例如 yield 7)。为了记住 yield 到底干了什么,最简单的方法是把它当作专门给生成器函数用的特殊的 return(加上点小魔法)。

比如我们用生成器编写一个 斐波那契数列 ,如下:

def fibb(num):
    n,a,b=0,0,1
    while True:
        if(n < num):
            yield b
            a,b = b,a+b
            n+=1
        else:
            break
    return  '结束'

c =fibb(5)
for i in c:
    print(i)
1
1
2
3
5

我们通过 next()获取上述代码的结果数据:

while True:
    try:
        n =next(c)
        print(n)
    except StopIteration as e:
        print('Gentration return value',e.value)
        break
1
1
2
3
5
Gentration return value 结束

注意:我们通过for循环调用generator时,发现拿不到generatorreturn语句的返回值。如果想要拿到返回值,必须捕获StopIteration错误,返回值包含在StopIteration的value中:


走进迭代器:

python中我们可通过for循环的集合数据类型 有 : strtupledictsetlist

还有一类数据是我们文章开始所述的generator ,包括生成器和带yieldgenerator function,这些可以直接作用于for循环的对象统称为可迭代对象:Iterable,可以使用isinstance()判断一个对象是否是Iterable对象:

isinstance('name',Iterable)
#true
isinstance((3,4,5),Iterable)
#true
isinstance([3,4,5],Iterable)
#true
isinstance({'name':'xiaohei','age':19},Iterable)
#true

如上:字符串,元祖,列表,字典皆为可迭代对象

生成器不但可以作用于for循环,还可以被next()函数不断调用并返回下一个值,直到最后抛出StopIteration。

可以被next()函数调用并不断返回下一个值的对象称为迭代器:Iterator

可以使用isinstance()判断一个对象是否是Iterator对象.

from collections import Iterator
isinstance([],Iterator)
#false
isinstance({},Iterator)
#false

生成器都是Iterator,list、set、dict、str 虽然都是可Iterable,但是他们都不是Iterator,可以通过iter()函数变成迭代器,如下:

isinstance(iter([]),Iterator)
#true
isinstance(iter({}),Iterator)
#true
isinstance(iter((3,4,5)),Iterator)
#true

疑惑:为什么list、dict、str等数据类型不是Iterator?

这是因为Python的Iterator对象表示的是一个数据流,Iterator对象可以被next()函数调用并不断返回下一个数据,直到没有数据时抛出StopIteration错误。可以把这个数据流看做是一个有序序列,但我们却不能提前知道序列的长度,只能不断通过next()函数实现按需计算下一个数据,所以Iterator的计算是惰性的,只有在需要返回下一个数据时它才会计算。

总结:

凡是可作用于for循环的对象都是Iterable类型

凡是可作用于next()函数的对象都是Iterator类型,它们表示一个惰性计算的序列;

集合数据类型如list、dict、str等是Iterable但不是Iterator,不过可以通过iter()函数获得一个Iterator对象。

相关参考:廖雪峰的官方网站 和http://www.oschina.net/translate/improve-your-python-yield-and-generators-explained


Leave Your Comment

电子邮件地址不会被公开。 必填项已用*标注