Python学习:迭代器与生成器

打印 上一主题 下一主题

主题 827|帖子 827|积分 2481

1.生成器

如果创建一个有很多元素的列表,但是只需要访问前几个元素,后面的元素占着的空间就白白浪费了
在循环的过程中不断推算出后续的元素呢?这样就不必创建完整的list,从而节省大量的空间。
在Python中,这种一边循环一边计算的机制,称为生成器:generator。
要创建一个generator,有很多种方法
第一种方法很简单,只要把一个列表生成式的[]改成(),就创建了一个generator:
  1. L = [x * x for x in range(10)]
  2. g = (x * x for x in range(10))
  3. print(L)#
  4. print(g)
复制代码
输出
  1. [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
  2. <generator object <genexpr> at 0x0000029277CA6AC0>
复制代码
可以直接打印出list的每一个元素,但怎么打印出generator的每一个元素
  1. print(next(g))
  2. print(next(g))
复制代码
输出
  1. 0
  2. 1
复制代码
如果要一个一个打印出来,可以通过next()函数获得generator的下一个返回值:
generator保存的是算法,比如我要知道计算第四次的值,next要调用四次,这太麻烦了
  1. for n in g:
  2.     print(n)
复制代码
输出
  1. 0
  2. 149162536496481
复制代码
如果要计算很多次,不断调用next实在是太变态了,正确的方法是使用for循环,generator也是可迭代对象,如果某些算法用列表生成式的for循环无法实现,还可以用函数,比如著名的斐波拉契数列(Fibonacci)
  1. def fib(max):
  2.     n,a ,b = 0,0,1#n是用来控制迭代的次数
  3.     while n < max:
  4.         yield b
  5.         a,b = b,a+b
  6.         n = n+1
  7.     return 'done'
复制代码
如果一个函数定义中包含yield关键字,那么这个函数就不再是一个普通函数,而是一个generator:
函数是顺序执行,遇到return语句或者最后一行函数语句就返回
在调用生成器运行的过程中,每次遇到 yield 时函数会暂停并保存当前所有的运行信息,返回 yield 的值, 并在下一次执行 next() 方法时从当前位置继续运行
  1. def add():
  2.     print('step 1')
  3.     yield 1
  4.     print('step 2')
  5.     yield (3)
  6.     print('step 3')
  7.     yield (5)
复制代码
在调用该generator时,首先要生成一个generator对象,然后用next()函数不断获得下一个返回值:
  1. o = add()
  2. print(next(o))
  3. print(next(o))
  4. print(next(o))
复制代码
输出
  1. step 1
  2. 1
  3. step 2
  4. 3
  5. step 3
  6. 5
复制代码
通过next调用时,遇到yield就停下了,再次调用在中断的地方继续往下执行
  1. for n in add():
  2.     print(n)
复制代码
输出
  1. step 1
  2. 1
  3. step 2
  4. 3
  5. step 3
  6. 5
复制代码
同样的,把函数改成generator后,基本上从来不会用next()来获取下一个返回值,而是直接使用for循环来迭代
要实验for需要把上面三次调用的代码删除,因为三次调用已经把函数全部执行完了,不会再去执行了。
但是用for循环调用generator时,发现拿不到generator的return语句的返回值
如果想要拿到返回值,必须捕获StopIteration错误,返回值包含在StopIteration的value中:
  1. g = fib(6)
  2. while True:
  3.     try:
  4.         x = next(g)
  5.         print('g:',x)
  6.     except StopIteration as e:
  7.         print('Generator return value:',e.value)
  8.         break
复制代码
generator的工作原理,它是在for循环的过程中不断计算出下一个元素,并在适当的条件结束for循环。对于函数改成的generator来说,遇到return语句或者执行到函数体最后一行语句,就是结束generator的指令,for循环随之结束
注意区分普通函数和generator函数,普通函数调用直接返回结果
generator函数的“调用”实际返回一个generator对象
2.迭代器

凡是可作用于for循环的对象都是Iterable类型;比如list,tuple,dict,set,str,generator等
判断是否为Iterable类型
  1. from collections import Iterable
  2. print(isinstance([], Iterable))
复制代码
输出
  1. True
复制代码
凡是可作用于next()函数的对象都是Iterator类型,它们表示一个惰性计算的序列;
直到最后抛出StopIteration错误表示无法继续返回下一个值了。

可以使用isinstance()判断一个对象是否是Iterator对象,生成器都是Iterator对象
  1. from collections import Iterator
  2. print(isinstance((x for x in range(10)), Iterator))
复制代码
输出
  1. True
复制代码
集合数据类型如list、dict、str等是Iterable但不是Iterator,不过可以通过iter()函数获得一个Iterator对象。
  1. '''
  2. 学习中遇到问题没人解答?小编创建了一个Python学习交流群:711312441
  3. 寻找有志同道合的小伙伴,互帮互助,群里还有不错的视频学习教程和PDF电子书!
  4. '''
  5. isinstance(iter([]), Iterator)
  6. isinstance(iter('abc'), Iterator)
  7. list=[1,2,3,4]
  8. it = iter(list)
  9. print(next(it))
  10. print(next(it))
  11. #迭代器也可以用常规for语句进行遍历
  12. for x in it:
  13.     print('当前计算:',x)#上面调用了两次,所以这边会接着上一次继续下去,只会打印两句,如果上面只
  14.     #调用一次,这里会调用三次
复制代码
输出
  1. 1
  2. 2
  3. 当前计算: 3
  4. 当前计算: 4
复制代码
3.总结

凡是可作用于for循环的对象都是Iterable类型   可迭代对象
凡是可作用于next()函数的对象都是Iterator类型,它们表示一个惰性计算的序列
集合数据类型如list、dict、str等是Iterable但不是Iterator,不过可以通过iter()函数获得一个Iterator对象
Python的for循环本质上就是通过不断调用next()函数实现的
比较使用list和generator保存斐波拉契数列运算结果的的区别
通过返回 List 能满足复用性的要求,但是当max函数运行占用的内存会随着max的增大而增大
  1. def fab(max):
  2.     n, a, b = 0, 0, 1
  3.     L = []
  4.     while n < max:
  5.         L.append(b)
  6.         a, b = b, a + b
  7.         n = n + 1
  8.     return L
  9. for n in fab(5):
  10.     print(n)
复制代码
输出
  1. 1
  2. 1
  3. 2
  4. 3
  5. 5
复制代码
一个带有 yield 的函数就是一个 generator,它和普通函数不同,生成一个 generator 看起来像函数调用,但不会执行任何函数代码,直到对其调用 next()(在 for 循环中会自动调用 next())才开始执行。虽然执行流程仍按函数的流程执行,但每执行到一个 yield 语句就会中断,并返回一个迭代值,下次执行时从 yield 的下一个语句继续执行。看起来就好像一个函数在正常执行的过程中被 yield 中断了数次,每次中断都会通过 yield 返回当前的迭代值。
yield 的好处是显而易见的,把一个函数改写为一个 generator 就获得了迭代能力,比起用类的实例保存状态来计算下一个 next() 的值,不仅代码简洁,而且执行流程异常清晰。
  1. def fab(max):
  2.     n, a, b = 0, 0, 1
  3.     while n < max:
  4.         yield b  # 使用 yield
  5.         # print b
  6.         a, b = b, a + b
  7.         n = n + 1
  8. for n in fab(5):
  9.     print(n)
复制代码
输出
  1. 1
  2. 1
  3. 2
  4. 3
  5. 5
复制代码
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

盛世宏图

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表