生成器

迭代器主要的作用就是迭代遍历,生成器主要的作用就是有条件的迭代

概念辨析

生成器其实就是一种特殊的迭代器。它使一种更为高级、更为优雅的迭代器。

仅仅在迭代至某个元素时才计算该元素,而在这之前或之后,元素可以不存在或者被销毁。

这个特点使得它特别适合用于遍历一些巨大的或是无限的集合,比如几个G的文件,或是斐波那契数列等等。这个特点被称为延迟计算或惰性求值(Lazy evaluation)。

生成器主要的特点就是:延迟操作,在需要的时候产生结果,而不是立即产生结果。

列表生成器

#先写一个列表a,可以随意取数据
a = [1,2,3,4,5]
print(a[0])
#打印1
#再生成一个列表,也可以随意取数据
b = [i+1 for i in range(5)]
print(b[3])
#打印3
#再生成一个生成器列表,无法随意取数据了
c =(i+1 for i in range(5))
print(c(3))
#打印 生成器c is not callble
#不能随意访问任意值。只能依次取
#可以看到,右边是一个函数,左边是这个函数的返回值,是一个生成器

为什么最后一个例子的()魔力这么大?可以阻止随意访问值?秘密在于:

#一个简单的生成器
a = (i for i in range(5))
#右边是一个生成器函数,返回值赋给左边,左边就是一个生成器
#为什么这个括号具有这么大的魔力?不仅按规定算法产生了元素,还让他们遍历?这难道不需要具备迭代器的基本素质?
#可以拆开上面的秘密,它的工作原理实际上是这样 #先给变量一个区间 def x(i):
for y in range(i): yield i b = x(5) #其实b就是a ,不过换了种写法

另外,这个yield看起来很强大,它阻止了随意访问,还让产生的数据可以迭代。它看起来至少应该是个迭代器,但它具体是什么?功能主要是什么?

上面例子中的列表c被称为列表生成式,已经具有生成器的特点,思考一下,这个式子很像我之前自己写的迭代器,都是通过一种算法来实现一串数据的生成

遗憾的是列表生成器中的算法是固定的:for语句控制下有限的几种变化。

是不是可以还是像写迭代器一样,把算法灵活化、开放化?同时保留列表生成器延迟这个特性?

这就诞生了生成器函数的需求。

那么要如何写一个生成器函数?

同时,留意到在定义上其实生成器是迭代器的子集,即生成器具有_iter_()和_next_()方法,那么在构想生成器的类时,不用再重复写这两个功能,只需要在_next_()中写明算法;再加上一个类似于可以随时暂停的功能——

yield,就是这个功能。yield如果出现在一个函数中,相当于为这个函数增添了三个功能:_iter_()和_next_()方法,以及挂起功能。即拥有yield的函数就是一个生成器——注意,生成器是拥有yield的函数的返回值,而不是这个函数自己。

(我认为语法糖就是一个打包的意思,把一堆功能浓缩为一个符号)

综上,一个生成器函数就写成了。

一个思考不知道对不对

在写一个迭代器的时候,会随着迭代器的调用迭代出所有值,加入有一万个值,就会非常浪费内存和时间。于是想,能不能有一种机制,把某一个值保存起来,需要时再用。

保存在哪儿?生成器中。把这种包含了迭代器功能同时还能保存值得,叫做生成器。通过一个生成器函数实现了算法,再把结果保存起来成为生成器,用生成器特有的语法调用

生成器

生成器函数的返回值,就是生成器。

 

理解逻辑

迭代器就是含有_next_()方法的对象,怎么产生迭代器?先iter一个可迭代对象,产生的返回值就是迭代器。b=iter(a),b就是一个迭代器

一个函数包含了yiled语句,它的返回值就是生成器

生成器一定是迭代器,因为它的设计是基于此;迭代器加了yield之后,再返回的值才是生成器。

迭代器和yield的关系:迭代器有next和iter,yield包含这两者,还有挂起功能。给一个什么都没有的函数加yield,不仅可以将它变为迭代器,还可以将它挂起。

 

下面为一个可以无穷生产奇数的生成器函数。

def odd():
    n=1
    while True:
        yield n
        n+=2
odd_num = odd()
count = 0
for o in odd_num:
    if count >=5: break
    print(o)
    count +=1
当然通过手动编写迭代器可以实现类似的效果,只不过生成器更加直观易懂,如下:

class Iter:
    def __init__(self):
        self.start=-1
    def __iter__(self):
        return self
    def __next__(self):
        self.start +=2 
        return self.start
I = Iter()#这一句是手动
for count in range(5):
    print(next(I))
题外话: 生成器是包含有__iter__()和__next__()方法的,所以可以直接使用for来迭代,而没有包含StopIteration的自编Iter来只能通过手动循环来迭代。

 

 

 

而生成器的法门就是yield,之前我们想让一个函数实现迭代功能,需要写一个类,再在类里面加入next和iter等等,但yiled语句不仅将这些过程打包成一个糖,

而且还新增了挂起功能。

比如本来你想实现一个rangeclass MyRange(collections.Iterator):
    def __init__(self, bound):
        self.cur = -1
        self.bound = bound
    def __iter__(self):
        return self
    def __next__(self):
        if self.cur < self.bound:
            self.cur += 1
            return self.cur
        else:
            raise StopIteration 
就需要写这些,就和java一样,非常麻烦。但是有了generator,def my_range(bound):
    for i in range(stop):
        yield i

 

先抄一些生成器的干货:

1.生成器的唯一注意事项就是:生成器只能遍历一次。

2.使用生成器以后,代码行数更少。——不看过程,只看结果

3.生成器表达式能做的事情列表解析基本都能处理,只不过在需要处理的序列比较大时,列表解析比较费内存。生成器可以通过延迟来减少内存占用和计算速度。

4.yield 的作用就是把一个函数变成一个 generator,带有 yield 的函数不再是一个普通函数,Python 解释器会将其视为一个 generator。

5.每次循环都会执函数内部的代码,执行到 yield语句 时, 函数就返回一个迭代值,下次迭代时,代码从 yield的下一条语句继续执行,而函数的本地变量看起来和上次中断执行前是完全一样的,于是函数继续执行,直到再次遇到 yield。看起来就好像一个函数在正常执行的过程中被 yield 中断了数次,每次中断都会通过 yield 返回当前的迭代值。

6.yield就是暂停/挂起的作用。生成器不会把结果保存在一个系列中,而是保存生成器的状态,在每次进行迭代时(调用next)返回一个值,直到遇到StopIteration异常结束。

 

posted @ 2018-01-13 21:47  凯曼  阅读(129)  评论(0)    收藏  举报