迭代器和生成器
# 最简单的生成器 l1 = [2, 4, 5, 6] # 利用生成器实现生成一个新列表,元素等于l内的每个元素+1 l2 = [x+1 for x in l1] print(type(l2)) # 结果:<class 'list'> print(l2) # 结果:[3, 5, 6, 7]
# 因为生成器会根据逻辑遍历可迭代对象(此处是l1)生成新的对象。这本身没什么问题,也符合我们的要求
# 但是如果可迭代对象的内容非常庞大例如:
# l3 = [x+1 for x in range(1000000)] # print(l3) # 直接生成新的对象后,存储下来。而对象在接下来会被调用,所以会保存在内存中
# 通过上面的例子,我们知道直接使用生成器如果遇到较大的迭代对象,就会占用非常大的内存
# 下面通过该为迭代器的方法来实现上述例子
# l4 = (x+1 for x in range(1000000)) # print(l4) # 结果是生成器:<generator object <genexpr> at 0x0000000001ED23C8> # 通过next(生成器)取值,取到最后一个值的时候,再次取值会报错 # l5 = (x+1 for x in l1) # print(next(l5)) # 结果是:3 # print(next(l5)) # 结果是:5 # print(next(l5)) # 结果是:6 # print(next(l5)) # 结果是:7 # print(next(l5)) # 此处生成器中已经没有多余的元素可以取了,所以会抛异常StopIteration,结果如下: """ Traceback (most recent call last): File "D:/02Project/pacho/15day/st_迭代.py", line 26, in <module> print(next(l5)) StopIteration """
# 为了避免此类情况的发生,可以采用for循环的的方式获取生成器的值,不会抛出异常,例子如下:
l6 = (x for x in l1) print(type(l6)) # 结果是:<class 'generator'> for i in l6: print(i) # 结果是 2 4 5 6
# 插入:偶然发现了一个错误的写法
l7 = (x for x in l1) for i in l7: print(next(l7)) # 结果是 4 6 原因是 for循环已经在调用next方法了,所以再次print(next(l7))实际是再次取一次值
# 好了步入正题了,下面看下典型应用——斐波那契数列
# 这里写一个生成斐波那契数列的函数
def fei(n): # 输出前n个数 x, y, z = 0, 0, 1 while True: if x < n: print(z) y, z = z, y + z x += 1 else: break return 'this is res' # 运行一下看看 fei(5) # 结果是: 1 1 2 3 5 fei(8) # 结果是: 1 1 2 3 5 8 13 21
# 现在我们不想让他一下输入所有结果,想让他每次调用输出一个结果(不要说什么根据递归重新写个方法,虽然也可行。。。)
f = fei(5) # 运行函数fei(5) 输出结果: 1 1 2 3 5 并把函数的返回值'this is res'赋值给f print(type(f)) # 结果是:<class 'str'> print(f) # 结果是:this is res
# 显然上面的操作并没有实现每次取他输出结果的功能,那么我们改一下上述函数,是他由一个普通函数变成生成器
def fei_1(n): # 输出前n个数 x, y, z = 0, 0, 1 while True: if x < n: yield z # 只是在这里吧输出改成了yield y, z = z, y + z x += 1 else: break return 'this is res' # 再看下输出结果 f = fei_1(5) # 运行函数fei_1(5) 没有输出结果,而是把函数变成了一个生成器 print(f) # 结果是:<generator object fei_1 at 0x0000000001EA23C8> 可以看到我们得到了一个生成器 print(type(f)) # 结果是:<class 'generator'> print(next(f)) # 结果是:1
# 我们写个简单的来看下 def f(n): for i in range(n): print('for start ...' if i == 0 else 'start start start') yield i print('for end ...' if i == n - 1 else 'end end end') return 'done' # n = f(3) # 得到了一个生成器 print(type(n)) # 结果:<class 'generator'> print(n) # 结果:<generator object f at 0x0000000001EA23C8> print(next(n)) # 结果:【for start ...】 【0】 print(next(n)) # 结果:【end end end】 【start start start】 【1】 print(next(n)) # 结果:【end end end】 【start start start】 【2】 【for end ...】 print(next(n)) # 生成器中没有元素,抛异常,导致一个尴尬的结果就是没有获取到函数f的返回值 """ Traceback (most recent call last): File "D:/02Project/pacho/15day/st_迭代.py", line 101, in <module> print(next(n)) # 结果:【end end end】 【start start start】 【1】 StopIteration: done """
# 从上面的例子我们就能够知道yield关键字生成器的作用:
# 1、通过yield关键字把一个函数变成生成器
# 2、每次调用会执行一次函数,并且执行到yield关键字后暂停
# 3、再次调用会从yield之后的代码开始执行
# 4、当生成器执行结束之后,才会返回函数的返回值
# 下面换一种能够获取到函数返回值的方法
gen = f(3) print(type(gen)) while True: # 循环取生成器的元素 try: i = next(gen) # 取生成器的元素 print(i) except StopIteration as e: # 当取不到的时候,捕获异常,并得到生成器函数的返回值 print(e) break # 结果是 """ <class 'generator'> for start ... 0 end end end start start start 1 end end end start start start 2 for end ... done """
# 好了,现在或过头来看自定义的生成器函数fei(生成斐波那契额数列,并且有返回值),该怎么调用呢,和上面一样
ff = fei_1(6) while True: try: print(next(ff)) except StopIteration as e: # 顺便加深理解了一下捕捉异常:如果调用某个生成器函数,捕获得到的异常e和e.value均为该函数的返回值 print(e) print(e.value) break # 结果如下: """ 1 1 2 3 5 8 this is res this is res """
参考:
前人种树后人乘凉,感谢分享:https://www.cnblogs.com/wj-1314/p/8490822.html