Python 迭代器与生成器

https://zhuanlan.zhihu.com/p/26123333

要完全理解透生成器,需要我们先掌握三个概念:

可迭代对象(Iterable)

迭代器(Iterator)

迭代(Iteration)

放一张图来理解,来自这里

额外提到了容器(container),说的是我们的集合类对象,如 list、set、dict,它们将多个元素组织在一起,这些对象就可以称为 container。

可迭代对象:

可直接作用于for循环的对象统称为Iterable 。具体的实现是,Python 中的对象只要定义了__iter__方法(该方法返回一个迭代器对象),或者定义了支持下标索引的__getitem__方法,那么这个对象就是可迭代对象。

>>>fromcollectionsimportIterable>>>isinstance([],Iterable)True>>>isinstance({},Iterable)True>>>isinstance([xforxinrange(10)],Iterable)True

迭代器:

可作用于next()函数的对象都是Iterator。具体的实现是,任何对象只要定义了__iter__和__next__方法,那就是迭代器对象;迭代器表示一个惰性计算的序列,需要__iter__返回迭代器自身,__next__返回迭代器中的下一个值,迭代到结尾时引发

StopIteration

异常;也就是说迭代器在遍历集合时,并不是将所有的元素事先都准备好,而是迭代到某个元素时才去计算该元素,利用这一特性我们可以去遍历一些巨大的集合,之前总结的函数式编程中,map,reduce,filter函数返回的就是一个新的迭代器。

还有一点需要明确的,迭代器都是可迭代对象,可迭代对象可以通过iter()返回一个新的迭代器。

>>>L=[1,2,3,4,5]>>>'__iter__'indir(L)True>>>'__next__'indir(L)False>>>newL=iter(L)>>>'__next__'indir(newL)True>>>newL.__next__()1>>>newL.__next__()2# 定义斐波拉契数的迭代器>>>classfib(object):...def__init__(self):...self.prev=0...self.curr=1...def__iter__(self):...returnself...def__next__(self):...value=self.curr...self.curr+=self.prev...self.prev=value...returnvalue...>>>f=fib()>>>foriinf:...ifi>20:break...print(i)...11235813

从上面的迭代操作中,可以看出 for 循环其实是调用__iter__获得迭代器,再调用__next__获取元素,迭代器内部状态保存在当前实例对象的prev以及cur属性中,在下一次调用中将使用这两个属性。每次调用next()方法都会执行以下两步操作:

修改状态,以便下次调用next()方法

计算当前调用的结果

迭代器的使用非常普通,Python的内置库itertools就是专门返回迭代器对象的,这篇博文专门介绍itertools库的,我从中列举了一些:

# 累加>>>importitertools>>>a=itertools.accumulate(range(10))>>>a>>>print(list(a))[0,1,3,6,10,15,21,28,36,45]# 连接列表或迭代器>>>c=itertools.chain(range(3),range(4),[0,1,2,3,4])>>>print(list(c))[0,1,2,0,1,2,3,0,1,2,3,4]# 按照真值表筛选元素>>>x=itertools.compress(range(5),(True,False,True,True,False))>>>print(list(x))[0,2,3]# 计数器,可以指定起始位置和步长>>>x=itertools.count(start=20,step=-1)>>>print(list(itertools.islice(x,0,10,1)))[20,19,18,17,16,15,14,13,12,11]# 按照分组函数的值对元素进行分组>>>x=itertools.groupby(range(10),lambdax:x<5orx>8)>>>forcondition,numbersinx:...print(condition,list(numbers))True[0,1,2,3,4]False[5,6,7,8]True[9]# 类似map>>>x=itertools.starmap(str.islower,'aBCDefGhI')>>>print(list(x))[True,False,False,False,True,True,False,True,False]

生成器:

有了前面的铺垫,我们就能更好地理解生成器了。生成器是什么?说白了生成器就是一种特殊的迭代器,不过它的实现方式更为简单优雅,同样我们可以明确的是,任何生成器都是迭代器,生成器也是一个惰性计算的序列。

我们来看看生成器的两种定义方式:

1、生成器表达式:

>>>[i*iforiinrange(5)]# 注意 Python3 中 range函数是迭代器[0,1,4,9,16]# 根据列表生成式,只需要简单修改就可以定义生成器>>>(i*iforiinrange(3))at0x7f59ed8fc3b8>

2、另一种定义复杂推导算法的生成器需要引入一个强大的关键字yield:

# 斐波那契序列的生成器函数>>> def fib():...    prev = curr = 1...    yield prev #1...    yield curr #2...    while True:...        prev, curr = curr, prev + curr...        yield curr... >>> f = fib()>>> f>>> for i in f:    # 还可以使用 next() 遍历生成器...    if i > 20: break...    print(i)... 11235813

分析一下流程:

调用生成器函数时只返回一个 generator 对象 f,函数并没有执行;

通过 for 循环生成器才开始执行,执行到 #1 yield prev 处,返回 yield 处的参数 prev,此时就打印出了1;

继续 for 循环,生成器函数将在上一次停止的语句处继续执行,遇到 #2 yield curr 返回,此时又打印出了1;

如此反复,直到i大于20跳出循环结束调用。

对比迭代器和生成器,实现同样的功能,生成器会显得更加优雅简洁。

迭代

一句话总结迭代:按照一定的顺序逐个访问容器中每一个元素的过程;也就是我们折腾斐波那契序列的过程

限于篇幅,生成器就介绍到这里,但生成器的威力远不止此,下一篇将通过生成器和 yield 引出协程和异步IO等。

推荐阅读更多精彩内容