天天看点

python生成器 协程 yield yield from

生成器概念;

         生成器: 函数中有yield 或者 yield from , 或者生成器表达式(x for x in range(5)) 都会被编译成生成器.

         生成器内部实现了迭代协议,即__iter__ , __next__  所以可以用于迭代.

         生成器不会像列表一样一次在内存中展开所有的值, 而是拿一次计算一次.

生成器是一个对象, 因此使用生成器需要调用一下函数来创建一个生成器对象,然后进行迭代

关于yield:

生成器的核心是 yield , 一旦运行到 yield 就暂停, 如要继续生成器内的代码, 需要外部驱动( next / send), 

next / send 都会先调用生成器__iter__ , 然后每次__next__

因此就目前为止先对生成器有一个 可暂停的印象即可;

同时需要注意的是 , 生成器函数一旦被调用 ,此生成器对象并不会马上执行代码 (跟函数的本质区别),需要你用next 来驱动一下,

生成器的结束方式也与普通不一样, 不论在生成器内部何时return / 执行到末尾 都会产生一个StopIteration的异常,来表示结束

关于yield from:  yield from实现

迭代完一个可迭代对象 (直到可迭代对象抛出StopIteration). 觉得有点绕 ? 往下拉,看例子

yield from 补充例子 : yield from 生成器 协程

第一个生成器例子:

def f1():
    print("f1")
    yield 1  # 会暂停在这里

if __name__ == '__main__':
    g = f1()
    print("f1:" , f1) #还是一个函数
    print("g:",g)   #这个是生成器啊

    value_from_yield = next(g) #来驱动一下
    print(value_from_yield)   

    #问题是, 此时生成器结束了吗?  没呢!!! 只是暂停了 , 想继续怎么办? next !!!

    try:
        next(g)   # 这时,yield 处返回,代码继续, 一旦生成器执行完会raise StopIteration
    except StopIteration as e:
        print(" g is done ")
        

    
           

下面开始把生成器和协程混合的讲:

协程由生成器进化来的,增强型的, 增强在哪里呢? 多了一个send,以及 yield不仅能产出值,还能接受值,

而生成器仅仅是一个生产者的角色:

def gen():
    for i in range(10):
        yield  i
           

因此,下面代码中把协程 和 生成器混在一起说,他们的行为模式都是一样的;

 生成器(协程)可以使用 next 来驱动代码继续 / send来交互.(驱动) .send 唯一比next多的一个功能就是发送一个值

 比如  

1.x = yield  这个表达式不产生值. 即在yield 后没有需要产出的值,默认None

2.x = yield 1 这个表达式会产出一个值

这里用 "产出" 而不使用 "返回值"这个说法, 是为了和return 区别.

而send(y) 的作用是 把 y 赋予 x(yeild 右边的) .

下面具体说明:

一个简单的例子:

"""
    内部有yield关键字,
    额外的函数调用 (send , close , throw)
    执行到yield 时, 函数暂停.
    注意 , yield 是产生一个值 , 不是返回一个值.
    需要激活一下, 使用next, 或send(None)
"""
def func(a):
    print('args , a:',a)
    b = yield a             #yield产生了a, 同时暂停了执行.此时, b 还未赋值
    print('recv b :' , b)
    c = yield a*b
    print('recv c:' , c)

m = func(1)  #调用一次,创建一个生成器对象,不会自动运行,与函数最本质的区别
print(inspect.getgeneratorstate(m))
print(next(m))                       #激活
print(m.send(2))
print(m.send(0))
#GEN_CREATED
#args , a: 1
#1
#recv b : 2
#2
#recv c: 0
           
#停止: close , throw
def demo_func():
    while 1:
        try:
            x = yield #暂停, 直到next/send
        except Exception:
            print('处理异常')
        else:
            print('接受到的值:',x)

m = demo_func()
next(m)             #激活
m.send(1)
m.send(2)
m.send(3)
m.close()  #给生成器抛一个StopIteration异常,让其结束
           

完整的例子:

"""
    一个完整的例子说明send的作用
"""
def grep(pattern):
    print('searching :',pattern)
    while 1:
        words = yield                    #运行到这里停止,把调用权交付给调用者
        if pattern in words:
            print('found:',pattern)
        else:
            print('not found')


pattern = input('>>>输入要查找的模式:')
co = grep(pattern)                      #产生一个生成器
next(co)                                #激活生成器 . 运行到yield
while 1:
    text = input('>>>输入一些文本:')
    co.send(text)                       #与协程交互
           

由于for 将自动先调用 __iter__ , 然后每次调用__next__ 来遍历 ,因此下面这个例子不再使用 [next/send->都调用__next__ ]

而一个对象想成为一个可迭代对象 , 重写 __iter__ 即可  , 那么让这个__iter__ 成为一个生成器对象是一个不错的选择:

class seq:
    def __init__(self, txt):
        self.text = txt

    def __iter__(self):  #既满足可迭代, 还是一个生成器
        for i in self.text:
            yield i

    # def __iter__(self):
    #    return (i for i in self.text)  #用生成器表达式

if __name__ == '__main__':

    s =seq("123") 

    # for 首先调用__iter__() , 此时返回一个生成器对象
    # for 每次对生成器调用__next__ (next) , yield 每次产出一个值
    for i  in s:
        print(i)
           

yield from 可用于等待一个生成器执行完, 凡是代码中出现yield from的也是一个生成器

#一个生成器函数
def iter_seq(text): 
    yield from text
    
if __name__ == '__main__':
    print("func:" , iter_seq) #函数
    g = iter_seq("123") #调用后成为生成器
    print("gen:" , g)

           

yield from 作用:

def iter_seq(text):
    yield from text


    #如果用yield来实现
    #for i in text:
     #   yield i


if __name__ == '__main__':
    print("func:" , iter_seq)
    g = iter_seq("123")
    print("gen:" , g)

    #新增的
    for i in g:
        print(i)
           

yield from 也将调用__iter__;

似乎好像除了代替一次迭代 以及 与yield 一样能产出值, 别的也没看出来啥, 是吗?

yield from 的核心功能之一就是迭代一个可迭代对象, 再来个例子:

def f1():
    print("f1")
    yield 1


def f2():
    print("f2")
    rs = yield f1()  #yield 一个生成器 , f1()此生成器没被执行
    yield 100 

if __name__ == '__main__':
    for i in f2():
        print(i)
           

发现没,  f1()这个生成器仅仅被产出来了, 并没有被执行,如果想执行怎么办?

def f1():
    print("f1")
    yield 1


def f2():
    print("f2")
    # rs = yield f1() 注释了这行
    for i in f1():
        print("in f2:",i)
    yield 100

if __name__ == '__main__':
    for i in f2():
        print(i)
           

现在再来想想yield from 的作用是什么? 迭代啊!

同时再想一下, 前面说了 生成器将抛出异常 StopIteration, 而 for 会处理这个情况,那么 yield from 内部也将处理这个异常

def f1():
    print("f1")
    yield 1


def f2():
    print("f2")
    yield from f1(); # 让f1生成器执行
    yield 100

if __name__ == '__main__':
    for i in f2():
        print("in main loop:",i)
           

在上面这个例子中, yield from 执行了f1()生成器, 更奇怪的是, f1()生成器yield 出来的值在 main loop 也打印出来了,

怎么回事呢?

先来说一下, yield from 可获取返回值(return), 新增了2行代码

def f1():
    print("f1")
    yield 1
    return "end" #增加了一个返回值


def f2():
    print("f2")
    ret = yield from f1();  # 获取返回值
    print("in f2 , after yield from :" , ret) 
    yield 100

if __name__ == '__main__':
    for i in f2():
        print("in main loop:",i)
           

yield from 作为一种中间层的双向通道:

def f1():
    print("f1")
    yield 1   # yield 1 后暂停
    return "end"


def f2():
    print("f2")
    ret = yield from f1(); #从 f1 yield获取的 1 再次yield出去后暂停
    print("in f2 , after yield from :" , ret)
    yield 100

if __name__ == '__main__':
    g = f2() #创建一个生成器对象

    #激活这个yield from的生成器,如果不调用next,f2生成器无法执行
    #而 yield from 将自动激活 f1
    value_from_yield = next(g) 


    #这个是从f1中yield出来的
    print("in main ,yield value from f1 :" , value_from_yield)
    time.sleep(3)
           

注意上面的代码,在main中 next(g) 后, 执行到f2的yield from f1(),由 yield  from 来激活f1(), 接着f1生成器

运行到yield 1,产值后 f1 暂停, f2获取到 f1 yield的值,再此产出,然后暂停.

此时 main 中 next 获取返回值,此值是由 yield from 所产出来的; 而 f1 ,f2 全部暂停运行;

因此, yield from 不仅拥有yield 的功能, 还能作为一种双向通道 : 

yield from实现

在上面的例子中多加一点代码:

def f1():
    print("f1")
    from_main = yield 1
    print("in f1 , from main:" , from_main)
    return "end"


def f2():
    print("f2")
    ret = yield from f1();
    print("in f2 , after yield from :" , ret)
    yield 100

if __name__ == '__main__':
    g = f2()
    value_from_yield = next(g)
    print("in main ,yield value from f1 :" , value_from_yield)
    time.sleep(3)
    value_from_yield = g.send("hello from main") #新增加的
    print("in main , value from f2 " ,value_from_yield)
           

上面新增的代码中 g.send , 将由yield from 转发到 f1 中的 from_main, 然后f1结束 ,f1抛出StopIteration异常,由 yield from

处理异常,并从异常中获取返回值, 接着 yield from 内部中断循环, 至此yield from 也结束, f2运行到 yield 100 暂停, 

因此在main中能获取到 100 , 此时虽然main已经结束, 但f2还处于暂停状态.

下面还有一个例子,自己看看能否分析一下?

#yield from 作为委派生成器
def get_total():
    totoal = 0
    while 1:
        print('子生成器进入循环')
        num = yield             # 到这里暂停了
        print('子生成器获取值:',num)
        if num is None:
            return totoal
        totoal += num

def gen(res_arr):
    print('委派生成器启动')
    while 1:
        print('委派生成器开始委托任务')
        res = yield from get_total() #到这里也暂停
        print('委派生成器获取值:',res)
        res_arr.append(res)

res_arr = []
g = gen(res_arr)
next(g)
g.send(1)
g.send(2)
g.send(None)
print('total:',res_arr)
           

继续阅读