Python之生成器详解

迭代

维基百科对迭代的定义如下

迭代是重复反馈过程的活动,其目的通常是为了接近并到达所需的目标或结果。每一次对过程的重复被称为一次“迭代”,而每一次迭代得到的结果会被用来作为下一次迭代的初始值。


可迭代对象(iterable) 与 迭代器(iterator)

关系如下图:

这里写图片描述

可迭代对象( iterable ) 是实现了__iter__()方法的对象
迭代器( iterator)是实现了__iter__()__next__()方法的对象
可迭代对象( iterable )通过调用 iter() 方法得到一个 迭代器( iterator)

可迭代对象的特点:能够使⽤for…in…的循环语法的对象,即 list、tuple、str 等类型的对象
迭代器对象的特点:该对象是消耗型的,即对该对象每遍历一次,就消失一个值,遍历完后,就变成了一个空的容器,但不等于 None。

通过如下代码,可以很清楚的认识可迭代对象迭代器的特点与联系

>>> from collections import Iterable, Iterator
>>> a = [1,2,3]   # 众所周知,list是一个iterable
>>> b = iter(a)   # 通过iter()方法,得到iterator,iter()实际上调用了__iter__(),此后不再多说
>>> isinstance(a, Iterable)
True
>>> isinstance(a, Iterator)
False
>>> isinstance(b, Iterable)
True
>>> isinstance(b, Iterator)
True
# 可见,itertor一定是iterable,但iterable不一定是itertor

# iterator是消耗型的,用一次少一次.对iterator进行变量,iterator就空了!
>>> c = list(b)
>>> c
[1, 2, 3]
>>> d = list(b)
>>> d
[]


# 空的iterator并不等于None.
>>> if b:
...   print(1)
...
1
>>> if b == None:
...   print(1)
...

# 再来感受一下next()
>>> e = iter(a)
>>> next(e)     #next()实际调用了__next__()方法,此后不再多说
1
>>> next(e)
2

然提到了for … in …语句,我们再来简单讲下其工作原理吧,或许能帮助理解以上所讲的内容

>>> x = [1, 2, 3]
>>> for i in x:
...     ...

我们对一个iterable用for … in …进行迭代时,实际是先通过调用iter()方法得到一个iterator,假设叫做X.然后循环地调用X的next()方法取得每一次的值,直到iterator为空,返回的StopIteration作为循环结束的标志 for … in … 会自动处理StopIteration异常,从而避免了抛出异常而使程序中断。如图所示
这里写图片描述


生成器

迭代器与生成器的区别:
1、生成器是一种特殊的迭代器,所以迭代器的用法可以用于生成器
2、要定义一个迭代器,需要分别实现__iter__()方法和__next__()方法
3、要定义生成器,只需要一个 yield

生成器,即带有yield的函数,是一种特殊的迭代器,包含生成器函数和生成器表达式(形如(elem for elem in [1, 2, 3])的表达式)

示例代码如下:

>>> a = (elem for elem in [1, 2, 3])
>>> a
<generator object <genexpr> at 0x7f0d23888048>
>>> def fib():
...     a, b = 0, 1
...     while True:
...         yield b
...         a, b = b, a + b
...
>>> b = fib()
<generator object fib at 0x7f0d20bbfea0>

生成器的调用方法:
示例代码:

>>> def gen():
...     while True:
...         s = yield
...         print(s)
...
>>> g = gen()
>>> g.send("kissg")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: can't send non-None value to a just-started generator
>>> next(g)
>>> g.send("kissg")
kissg

问题所在:调用 send(value) 时要注意,要确保 generator 是在yield 处被暂停了,如此才能向 yield 表达式传值,否则将会报错(如上所示),可通过 next() 方法或send(None) 使generator执行到yield。

再来看一段yield更复杂的用法,或许能加深你对generator的next()与send(value)的理解。

>>> def echo():
...     for row in range(5):
...         s = yield row   #若此处 yield 后面无参数,则默认值为 None。通过send(value)方法将value作为yield表达式的当前值
...         print("This value is: ", value)
...
>>> g = echo()
>>> g.send(None)  # 或者换成 next(g)
0
>>> g.send("hello")
This value is: hello
1
>>>
>>> g.send("world")    # 获取 s = yield row 中的 s 值
This value is: world
2
>>>
>>> next(g)            # 获取 echo() 对象的值
This value is: None
3
>>>
>>> next(g)
This value is: None
4

使用生成器实现生产者消费者模式,代码如下:

import threading
import time

# 消费者
def consumer():
    data = ''
    while True:
        r = yield data
        data = '生产的'+r+'还不错'
        time.sleep(1)

# 生产者
def produce(con):
    con.send(None)   #可通过 next() 方法或send(None) 使生成器执行到yield,才能进行下一步生产消费操作
    for i in range(5):
        print('生产者生产了%d' % i)
        r = con.send(str(i))
        print(r)
if __name__ == '__main__':
    m = consumer()
    produce(m)

综上,yield 作为一个暂停恢复的点,代码从yield处恢复,又在下一个yield处暂停。可见,在一次 next() (非首次)或 send(value) 调用过程中,实际上存在2个 yield,一个作为恢复点的 yield 与一个作为暂停点的 yield。因此,也就有2个yield表达式。send(value) 方法是将值传给恢复点yield,调用next()表达式的值时,其恢复点yield的值总是为None,而将暂停点的yield表达式的值返回。

使用yield实现协程

#!/usr/bin/python

def consumer():
    r = ''
    while True:
        n = yield r
        if not n:
            print("not n...")
            return
        print('[CONSUMER] Consuming %s...' % n)
        r = '200 OK'

def produce(c):
    f = c.send(None)
    print('[PRODUCER] Consumer first return: %s' % f)
    n = 0
    while n < 2:
        n = n + 1
        print('[PRODUCER] Producing %s...' % n)
        r = c.send(n)
        print('[PRODUCER] Consumer return: %s' % r)
    c.close()

c = consumer()
produce(c)

运行结果:

[PRODUCER] Consumer first return: 
[PRODUCER] Producing 1...
[CONSUMER] Consuming 1...
[PRODUCER] Consumer return: 200 OK
[PRODUCER] Producing 2...
[CONSUMER] Consuming 2...
[PRODUCER] Consumer return: 200 OK

协程的说明:

注意到consumer函数是一个generator,把一个consumer传入produce后:

  1. 首先调用c.send(None)启动生成器;
  2. 然后,一旦生产了东西,通过c.send(n)切换到consumer执行;
  3. consumer通过yield拿到消息,处理,又通过yield把结果传回;
  4. produce拿到consumer处理的结果,继续生产下一条消息;
  5. produce决定不生产了,通过c.close()关闭consumer,整个过程结束。

整个流程无锁,由一个线程执行,produce和consumer协作完成任务,所以称为“协程”,而非线程的抢占式多任务。

参考文档:
http://kissg.me/2016/04/09/python-generator-yield/
https://www.cnblogs.com/fangyuan1004/p/4571304.html

  • 7
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值