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)
fib

 

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

 

 

 

posted @ 2017-06-18 01:22  seaidler  阅读(115)  评论(0)    收藏  举报