一、列表生成式
在学习生成器迭代器之前先了解一下什么是列表生成式,列表生成式是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)
...
>>>