Python 迭代器与生成器

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

  1. 可迭代对象(Iterable)

  2. 迭代器(Iterator)

  3. 迭代(Iteration)

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

《Python 迭代器与生成器》
《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 = self.curr
...         self.curr += self.prev
...         self.prev = value
...         return value
... 
>>> f = fib()
>>> for i in f:
...     if i > 20: break
...     print(i)
... 
1
1
2
3
5
8
13

从上面的迭代操作中,可以看出 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)
... 
1
1
2
3
5
8
13

分析一下流程:

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

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

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

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

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

迭代

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

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

    原文作者:驭风者
    原文地址: https://zhuanlan.zhihu.com/p/26123333
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞