这部分理解起来比较费劲,多练习,多思考~
一 生成器
我们知道列表的大小是有限制的,假设我们要放自然数0~100万,那这个列表肯定占了很大内存空间,而且我们也可能只用到其中很小一部分的数据,这样的话我们用列表造成的浪费就很大了。但是,如果这个列表的元素是有规律可循的,那我们就可以根据这个规律,推算到任何一个元素,不必创建完整的list,从而节省大量的空间。在Python里,这种一边循环一边计算的机制,称为生成器generator。
- 第一种简单的generator,是把列表生成式的[ ] 改成()即可
L = [i * i for i in range(1,11) if i%2==0]
print(L) # [4, 16, 36, 64, 100]
g=(i * i for i in range(1,11) if i%2==0)
print(g) # <generator object <genexpr> at 0x000002D6BDBADBF8>
通过next(g)可以打印出generator中的元素:
print(next(g)) #4
print(next(g)) #16
print(next(g)) #36
print(next(g)) #64
print(next(g)) #100
# print(next(g)) #StopIteration 没有足够的元素,调用next会抛出StopIteration。
当然更简单的是通过for循环遍历generator,同时还防止了StopIteration:
g=(i * i for i in range(1,11) if i%2==0)
for i in g:
print(i)
输出:
4
16
36
64
100
能用for遍历,说明generator是个可迭代对象。
- 当规律更复杂,不能用列表生成式那样简单生成,可以用带yield 的函数来生成。一旦函数带了yield 关键字,那这个函数就不是通常的函数了,而是generator。
def func():
print('step 1')
yield 1
print('step 2')
yield (2)
print('step 3')
yield (3)
f=func()
print(next(f))
print(next(f))
print(next(f))
看下输出,分析下结果:
step 1
1
step 2
2
step 3
3
第一次执行next(f),函数内部打印了 step 1,再执行了 yield 1。而且这个1,整好被我们print打印出来了。也就是说next(f)返回了1,结果上类似于return 1 了,此时函数终止在这里。
第二次执行next(f),函数继续往下走,先打印step 2,再执行yield (2),函数终止,返回 2,并打印。
第三次执行next(f),函数继续往下走,先打印step 3,再执行yield (3),函数终止,返回 3,并打印。
可以得出结论:带yield的generator,遇到yield函数终止,并返回相应的值。这就是规则。
等同于用for来遍历:
f=func()
for i in f:
print(i)
运行结果完全一样。
好像有点乱~对于一个generator来说,用for遍历的效果等同于不停的调用next(忽略异常)。
到这里,我们只需要清楚生成器是什么(一边循环一边计算的机制),他的两种常见形式,如何打印元素,执行流程即可。
二 迭代器
像上面讲到的generator,可以被next()函数调用并不断返回下一个值的对象称为迭代器:Iterator。
除此之外,像集合数据类型,如list、tuple、dict、set、str等,可以直接作用于for循环的对象统称为可迭代对象:Iterable。generator也可以直接作用于for,所以generator同时也是Iterable。
生成器都是Iterator对象,但list、dict、str虽然是Iterable,却不是Iterator。因为Python的Iterator对象表示的是一个数据流,通过next()函数调用并不断返回下一个数据。所以可以把他看成一个有序的,未知大小的序列,可以看成无限大,而我们常见的集合型对象都是有限大小的。