17-1 生成器、迭代器
一、概述
Python的数据结构中,容器(container)、可迭代对象(iterable)、迭代器(iterator)、生成器(generator)、列表/集合/字典推导式(list,set,dict comprehension)众多概念的关系如下图:

二、容器(container)
容器是一种把多个元素组织在一起的数据结构,容器中的元素可以逐个地迭代获取,可以用 in , not in 关键字判断元素是否包含在容器中。通常这类数据结构把所有的元素存储在内存中(也有一些特列并不是所有的元素都放在内存)在Python中,常见的容器对象有:
- list, deque, ....
- set, frozensets, ....
- dict, defaultdict, OrderedDict, Counter, ....
- tuple, namedtuple, …
- str
容器比较容易理解,因为你就可以把它看作是一个盒子、一栋房子、一个柜子,里面可以塞任何东西。从技术角度来说,当它可以用来询问某个元素是否包含在其中时,那么这个对象就可以认为是一个容器,比如 list,set,tuples都是容器对象:
print(1 in [1, 2, 3]) # list 为容器
print(4 not in [1, 2, 3])
print(1 in {1, 2, 3}) # set 为容器
print(1 in (1, 2, 3)) # tuple 为容器
三、 可迭代对象(iterable)
如果给定一个list或tuple,我们可以通过for循环来遍历这个list或tuple,这种遍历我们称为迭代(Iteration)。
刚才说过,很多容器都是可迭代对象,此外还有更多的对象同样也是可迭代对象,比如处于打开状态的files,sockets等等。但凡是可以返回一个迭代器的对象都可称之为可迭代对象,听起来可能有点困惑,没关系,可迭代对象与迭代器有一个非常重要的区别。先看一个例子:
x = [1, 2, 3] y = iter(x) z = iter(x) print(next(y)) # 输出:1 print(next(y)) # 输出:2 print(next(z)) # 输出:1 print(type(x)) # 输出:<class 'list'> print(type(y)) # 输出:<class 'list_iterator'>
这里 x 是一个可迭代对象,可迭代对象和容器一样是一种通俗的叫法,并不是指某种具体的数据类型,list是可迭代对象,dict是可迭代对象,set也是可迭代对象。 y 和 z 是两个独立的迭代器,迭代器内部持有一个状态,该状态用于记录当前迭代所在的位置,以方便下次迭代的时候获取正确的元素。迭代器有一种具体的迭代器类型,比如 list_iterator , set_iterator 。可迭代对象实现了 __iter__ 和 __next__ 方法(python2中是 next 方法,python3是 __next__ 方法),这两个方法对应内置函数 iter() 和 next() 。 __iter__ 方法返回可迭代对象本身,这使得他既是一个可迭代对象同时也是一个迭代器。
四、迭代器(iterator)
那么什么迭代器呢?它是一个带状态的对象,他能在你调用 next() 方法的时候返回容器中的下一个值,任何实现了 __next__() (python2中实现 next() )方法的对象都是迭代器,至于它是如何实现的这并不重要。
五、生成器(Generator)
如果把列表看做已经做好的一桌子菜,生成器可以看成是做这桌子菜的厨师,吃的时候,他再按这桌菜的菜单依次做出菜。
1. 创建生成器
- 简单生成器
只要把一个列表生成式的[]改成(),就创建了一个generator:
g = (x * x for x in range(5)) # 注意区分[x * x for x in range(5)]为列表生成式 print(g) # 屏幕输出:<generator object <genexpr> at 0x002D5E40>
- 带yield语句的生成器
如果一个函数定义中包含yield关键字,那么这个函数就不再是一个普通函数,而是一个generator:
def gen():
yield 1
yield 2
yield 3
g = gen() # 输出:<generator object gen at 0x00225E40>
generator和函数的执行流程不一样。函数是顺序执行,遇到return语句或者最后一行函数语句就返回。而变成generator的函数,在每次调用next()的时候执行,遇到yield语句返回,再次执行时从上次返回的yield语句处继续执行。
2. 生成器的调用
第一次执行next(generator)时,会执行完yield语句后程序进行挂起,所有的参数和状态会进行保存。再一次执行next(generator)时,会从挂起的状态开始往后执行。在遇到程序的结尾或者遇到StopIteration时,循环结束:
g = (x * x for x in range(5))
print(g) # 输出:<generator object <genexpr> at 0x002D5E40>
print(next(g)) # 输出:0
print(next(g)) # 输出:1
print('在中间可以做其他的事') # 输出:在中间可以做其他的事
print(next(g)) # 输出:4
print(next(g)) # 输出:9
print(next(g)) # 输出:16
#print(next(g)) # 打印此句,报错:StopIteration
当然,不断调用next(g)实在是太变态了,正确的方法是使用for循环(简便且不会报错),因为generator也是可迭代对象:
g = (x * x for x in range(5))
for i in g:
print(i)
def fib(max_num): n, a, b = 0, 0, 1 while n < max_num: yield a a, b = b, a+b # 等号右边的值先计算出来结果,然后再赋给左边 n += 1 for i in fib(6): print(i)
3. 生成器支持的方法
- close()
手动关闭生成器函数,后面的调用会直接返回StopIteration异常。
def gen():
yield 1
yield 2
yield 3
print(next(g))
g.close()
print(next(g)) # 报错:StopIteration
- send()
生成器函数最大的特点是可以接受外部传入的一个变量,并根据变量内容计算结果后返回。协程的实现靠它。
def gen():
value = 0
while True:
receive= yield value
if receive == 'e':
break
value = 'got: %s' % receive
g=gen ()
print(g.send(None)) # 输出:0 g.send(None)等价于next(g)
print(g.send('aaa')) # 输出:got: aaa
print(g.send(3)) # 输出:3
print(g.send('e')) # 报错:StopIteration
执行流程:
(1)通过g.send(None)或者next(g)可以启动生成器函数,并执行到第一个yield语句结束的位置。此时,执行完了yield语句,但是没有给receive赋值。yield value会输出初始值0注意:在启动生成器函数时只能send(None),如果试图输入其它的值都会得到错误提示信息。
(2)通过g.send(‘aaa’),会传入aaa,并赋值给receive,然后计算出value的值,并回到while头部,执行yield value语句有停止。此时yield value会输出”got: aaa”,然后挂起。
(3)通过g.send(3),会重复第2步,最后输出结果为”got: 3″
(4)当我们g.send(‘e’)时,程序会执行break然后推出循环,最后整个函数执行完毕,所以会得到StopIteration异常。
import time def consumer(name): print("%s 准备吃包子啦!" %name) while True: baozi = yield print("包子[%s]来了,被[%s]吃了!" %(baozi,name)) def producer(name): c = consumer('A') c2 = consumer('B') next(c) next(c2) print("老子开始准备做包子啦!") for i in range(10): time.sleep(1) print("做了2个包子!") c.send(i) c2.send(i) producer("alex")
参考:
http://www.cnblogs.com/yuanchenqi/articles/5769491.html
浙公网安备 33010602011771号