生成器
迭代器主要的作用就是迭代遍历,生成器主要的作用就是有条件的迭代
概念辨析
生成器其实就是一种特殊的迭代器。它使一种更为高级、更为优雅的迭代器。
仅仅在迭代至某个元素时才计算该元素,而在这之前或之后,元素可以不存在或者被销毁。
这个特点使得它特别适合用于遍历一些巨大的或是无限的集合,比如几个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异常结束。

浙公网安备 33010602011771号