Python 迭代器与生成器

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

  1. 可迭代对象(Iterable)
  2. 迭代器(Iterator)
  3. 迭代(Iteration)
放一张图来理解,来自这里
Python 迭代器与生成器

文章插图
 
额外提到了容器(container),说的是我们的集合类对象,如 list、set、dict,它们将多个元素组织在一起,这些对象就可以称为 container 。
可迭代对象:
可直接作用于for循环的对象统称为Iterable。具体的实现是,Python 中的对象只要定义了__iter__方法(该方法返回一个迭代器对象),或者定义了支持下标索引的__getitem__方法,那么这个对象就是可迭代对象 。
>>> from collections import Iterable>>> isinstance([], Iterable)True>>> isinstance({}, Iterable)True>>> isinstance([x for x in range(10)], Iterable)True迭代器:
可作用于next()函数的对象都是Iterator 。具体的实现是,任何对象只要定义了__iter__和__next__方法,那就是迭代器对象;迭代器表示一个惰性计算的序列,需要__iter__返回迭代器自身,__next__返回迭代器中的下一个值,迭代到结尾时引发 StopIteration 异常;也就是说迭代器在遍历集合时,并不是将所有的元素事先都准备好,而是迭代到某个元素时才去计算该元素,利用这一特性我们可以去遍历一些巨大的集合,之前总结的函数式编程中,map,reduce,filter函数返回的就是一个新的迭代器 。
还有一点需要明确的,迭代器都是可迭代对象,可迭代对象可以通过iter()返回一个新的迭代器 。
>>> L = [1, 2, 3, 4, 5]>>> '__iter__' in dir(L)True>>> '__next__' in dir(L)False>>> newL = iter(L)>>> '__next__' in dir(newL)True>>> newL.__next__()1>>> newL.__next__()2# 定义斐波拉契数的迭代器>>> class fib(object):... def __init__(self):... self.prev = 0... self.curr = 1... def __iter__(self):... return self... def __next__(self):... value = https://www.isolves.com/it/cxkf/yy/Python/2019-05-15/self.curr... self.curr += self.prev... self.prev = value... return value... >>> f = fib()>>> for i in f:... if i > 20: break... print(i)... 11235813从上面的迭代操作中,可以看出 for 循环其实是调用__iter__获得迭代器,再调用__next__获取元素,迭代器内部状态保存在当前实例对象的prev以及cur属性中,在下一次调用中将使用这两个属性 。每次调用next()方法都会执行以下两步操作:
  1. 修改状态,以便下次调用next()方法
  2. 计算当前调用的结果
迭代器的使用非常普通,Python的内置库itertools就是专门返回迭代器对象的,这篇博文专门介绍itertools库的,我从中列举了一些:
# 累加>>> import itertools>>> a = itertools.accumulate(range(10))>>> a<itertools.accumulate object at 0x7fb030907388>>>> 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), lambda x: x < 5 or x > 8)>>> for condition, numbers in x: ... 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 * i for i in range(5)] # 注意 Python3 中 range函数是迭代器[0, 1, 4, 9, 16]# 根据列表生成式,只需要简单修改就可以定义生成器>>> (i * i for i in range(3))<generator object <genexpr> at 0x7f59ed8fc3b8>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<generator object fib at 0x7f85cca9d410>>>> for i in f:# 还可以使用 next() 遍历生成器... if i > 20: break... print(i)... 11235813


推荐阅读