生成器generator和迭代器Iterator
Posted ceo-python
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了生成器generator和迭代器Iterator相关的知识,希望对你有一定的参考价值。
一、列表生成式
在学习生成器迭代器之前先了解一下什么是列表生成式,列表生成式是Python内置的非常简单却强大的可以用来创建list的生成式。什么意思?举个例子,如果想生成列表[0,1,2,3,4,5]可以使用list(range(6)),但是如果想要生成[,,,,,]即[0,1,4,9,16,25]怎么做?
#方法一:循环
>>> L = []
>>> for x in range(6):
... L.append(x**2)
...
>>> L
[0, 1, 4, 9, 16, 25]
#方法二:列表生成式
>>> [x**2 for x in range(6)]
[0, 1, 4, 9, 16, 25]
观察方法一、方法二可以发现,使用方法二列表生成式更为简洁、明了,方法二就是列表生成式。再来几个例子加深理解:
#计算正整数0-5之间偶数平方
>>> [x**2 for x in range(6) if x%2==0]
[0, 4, 16]
#列表生成式使用两个变量
>>> d = {‘a‘:‘1‘,‘b‘:‘2‘,‘c‘:‘3‘}
>>> [k+‘=‘+v for k,v in d.items()]
[‘a=1‘, ‘b=2‘, ‘c=3‘]
二、生成器
通过列表生成式,可以直接创建一个列表。但是,受到内存限制,列表容量肯定是有限的。而且,创建一个包含100万个元素的列表,不仅占用很大存储空间,如果我们仅仅需要访问前面几个元素,那后面绝大多数元素占用的空间都白白浪费了。
所以,如果列表元素可以按照某种算法推算出来,那是否可以在循环过程中不断推算后续元素呢?这样就不必创建完整的list,从而节省大量空间。在Python中,这种一边循环一边计算的机制,称为生成器:generator。
1.如何创建生成器
方法一:创建生成器最简单的方法就是把一个列表生成式的 [] 改为 () ,就创建了一个生成器:
#列表生成式 >>> L = [x**2 for x in range(6)] >>> L [0, 1, 4, 9, 16, 25] #生成器 >>> g = (x**2 for x in range(6)) >>> g <generator object <genexpr> at 0x00000173FFBFA1A8> 注:L与g的区别仅在于最外层的[]和(),L是一个列表,而g是一个生成器。
方法二:要把普通函数变为生成器(generator),只需把 print(b) 改为 yield b 就可以了。 如果一个函数中包含yield关键字,那么这个函数就不再是一个普通函数,而是一个生成器(generator)。
2.如何获得生成器的每一个元素?
方法一:如果要一个一个打印出来,可以通过 next() 函数获得生成器的下一个返回值。生成器保存的是算法,每次调用next(),就计算g的下一个元素的值,直到计算到最后一个元素,没有更多元素时,抛出StopIteration错误。
#通过next()获得生成器的每一个元素 >>> g = (x**2 for x in range(6)) >>> g <generator object <genexpr> at 0x00000173FFBFA1A8> >>> next(g) 0 >>> next(g) 1 >>> next(g) 4 >>> next(g) 9 >>> next(g) 16 >>> next(g) 25 >>> next(g) Traceback (most recent call last): File "<stdin>", line 1, in <module> StopIteration
方法二:使用for循环
#通过for循环获得生成器的每一个值 >>> g = (x**2 for x in range(6)) >>> for i in g: ... print(i) ... 0 1 4 9 16 25
3.生成器实例
练习一:著名的斐波那契数列(Fibonacci),除第一个和第二个数外,任意一个数都可以由前两个数相加得到:1,1,2,3,5,8,13,21,34...用列表生成式写不出来,如何用函数实现?
#方法一: >>> def fib(max): ... n,a,b = 0,0,1 ... while n < max: ... print(b) ... a,b = b,a+b ... n += 1 ... return ‘done‘ ... >>> fib(6) 1 1 2 3 5 8 ‘done‘ #注:a,b = b,a+b 相当于 t = (b,a+b) a = t[0] b = t[1]
#方法二:生成器 >>> def fib(max): ... n,a,b = 0,0,1 ... while n < max: ... yield b ... a,b = b,a+b ... n += 1 ... return ‘done‘ ... >>> f = fib(6) >>> f <generator object fib at 0x00000173FFBFA1A8> >>> for i in f: ... print(i) ... 1 1 2 3 5 8
最难以理解的就是生成器与函数的执行流程不一样。函数是顺序执行,遇到 return 语句或者最后一行函数语句就返回。而变成生成器的函数,在每次调用 next() 的时候执行,遇到 yield 语句返回,再次执行时从上次返回的 yield 语句处继续执行。
#生成器函数每次调用next()的时候执行,遇到yield语句返回,再次执行时从上次返回的yield语句处继续执行 >>> def odd(): ... print(‘第一步‘) ... yield 100 ... print(‘第二步‘) ... yield 200 ... print(‘第三步‘) ... yield 300 ... >>> g = odd() >>> next(g) 第一步 100 >>> next(g) 第二步 200 >>> next(g) 第三步 300 >>> next(g) Traceback (most recent call last): File "<stdin>", line 1, in <module> StopIteration >>> #odd()生成器函数,在执行过程中遇到yield就中断,下次继续执行。执行3次yield后,已经没有yield可以执行,所以第4次调用报错。
生成器总结:
a、在Python中可以简单地把列表生成式改为generator,也可以通过函数实现复杂逻辑的generator。生成器的工作原理是在for循环过程中不断计算出下一个元素,并在适当条件结束for循环。对于函数改成的generator来说,遇到return语句或者执行到函数体最后一行语句,就是结束generator的指令,for循环随之结束。
b、普通函数和生成器函数区别:普通函数调用直接返回结果,而生成器函数的“调用”实际返回一个生成器对象
#普通函数 >>> r = abs(-1) >>> r 1 #生成器函数 >>> g = fib(6) >>> g <generator object fib at 0x00000173FFBFA1A8>
三、迭代器
可以直接作用于 for 循环的数据类型有以下几种:
一类是集合数据类型,如list、tuple、dict、set、str 等;
一类是generator,包括生成器和带 yield 的generator function。
这些可以直接作用于for循环的对象统称为可迭代对象:Iterable。可以使用isinstance()判断一个对象是否是Iterable对象:
#判断一个对象是否是可迭代对象Iterable >>> from collections import Iterable >>> isinstance([],Iterable) True >>> isinstance((),Iterable) True >>> isinstance({},Iterable) True >>> isinstance(‘aaa‘,Iterable) True >>> isinstance((x for x in range(5)),Iterable) True >>> isinstance(100,Iterable) False >>> isinstance(True,Iterable) False
可以被 next() 函数调用并不断返回下一个值的对象称为迭代器:Iterator。可以使用isinstance()判断一个对象是否是Iterator对象:
#判断一个对象是否是迭代器:Iterator >>> from collections import Iterator >>> isinstance([],Iterator) False >>> isinstance((),Iterator) False >>> isinstance({},Iterator) False >>> isinstance(‘aaa‘,Iterator) False >>> isinstance((x for x in range(5)),Iterator) True
可以发现生成器都是迭代器(Iterator)对象,但list、tuple、dict、str虽然是可迭代对象(Iterable),却不是迭代器(Iterator)。那有没有什么方法可以使list、tuple、dict、str等可迭代对象(Iterable)变成迭代器(Iterator)? iter() 函数可以做这件事!
#iter()将list、tuple、dict、set、str等可迭代对象变为迭代器 >>> isinstance(iter([]),Iterator) True >>> isinstance(iter(()),Iterator) True >>> isinstance(iter({}),Iterator) True >>> isinstance(iter(‘aaa‘),Iterator) True
为什么list、dict、str等数据类型不是Iterator?这是因为Python的迭代器(Iterator)对象表示的是一个数据流,迭代器(Iterator)对象可以被next() 函数调用并不断返回下一个数据,直到没有数据时抛出StopIteration错误。可以把这个数据流看做是一个有序序列,但我们不能提前知道序列的长度,只能不断通过next()函数实现按需计算下一个数据,所以Iterator的计算是惰性的,只有在需要返回下一个数据时它才会计算。
迭代器(Iterator)甚至可以表示一个无限大的数据流,例如全体自然数。而使用list是永远不可能存储全体自然数的。
迭代器总结:
a、凡是可作用于 for 循环的对象都是可迭代(Iterable) 类型;
b、凡是可作用于 next() 函数的对象都是 迭代器(Iterator) 类型,它们表示一个惰性计算的序列;
c、集合数据类型如list、dict、str等都是可迭代(Iterable)但不是迭代器(Iterator),不过可以通过iter()函数获得一个迭代器(Iterator)对象。
d、Python的for循环本质上就是通过不断调用next()函数实现的。
for x in [1, 2, 3, 4, 5]: pass 实际上完全等价于: # 首先获得Iterator对象: it = iter([1, 2, 3, 4, 5]) # 循环: while True: try: # 获得下一个值: x = next(it) except StopIteration: # 遇到StopIteration就退出循环 break
四、迭代器生成器关系
1.对于可迭代对象,可以通过 iter() 函数获得迭代器对象,并且可以被next() 函数调用并不断返回下一个数据,直到没有数据时抛出StopIteration错误。
2.生成器是一种特殊的迭代器,生成器通过生成器函数产生,生成器函数可以通过常规的def语句来定义,但是不用return返回,而是用yield一次返回一个结果。return 与 yield 不同之处在于return返回后,函数会释放,而生成器不会。在直接调用next()方法或者用 for 语句进行下一次迭代时,生成器会从yield 下一句开始执行,直至遇到下一个 yield。
3.迭代器可以多次迭代,而生成器只能迭代一次。
#迭代器:可以多次迭代 >>> g = [x*x for x in range(5)] >>> g [0, 1, 4, 9, 16] >>> for i in g: ... print(i) ... 0 1 4 9 16 >>> g [0, 1, 4, 9, 16] >>> for i in g: ... print(i) ... 0 1 4 9 16 #生成器:只能迭代一次 >>> f = (x*x for x in range(5)) >>> f <generator object <genexpr> at 0x0000022BA065F0F8> >>> for i in f: ... print(i) ... 0 1 4 9 16 >>> f <generator object <genexpr> at 0x0000022BA065F0F8> >>> for i in f: ... print(i) ... >>>
以上是关于生成器generator和迭代器Iterator的主要内容,如果未能解决你的问题,请参考以下文章
ES6中的迭代器(Iterator)和生成器(Generator)
ES6中的迭代器(Iterator)和生成器(Generator)