资讯专栏INFORMATION COLUMN

生成器进化到协程 Part 1

lemon / 484人阅读

摘要:生成器用于定义生成器函数只要存在该函数必定是一个生成器调用该函数返回一个生成器让一个生成器前进使用使一个生成器前进到下一个语句处,并将产出值作为其返回值。

前言

这篇文章大部分来自 David Beazley 在 PyCon 2014 的 PPT 《Generators: The Final Frontier》。这个PPT很长而且非常烧脑,建议在阅读前应了解 Python 的生成器与携程相关知识,推荐《流畅的 Python》。

David Beazley 的博客

PPT 下载链接

生成器(generator)

使用 yield 来定义一个生成器

def countdown(n):
    while n > 0:
        yield n
        n -= 1
    
c = countdown(10)
c

生成器可用于迭代

for x in countdown(10):
    print("倒数:", x)
倒数: 10
倒数: 9
倒数: 8
倒数: 7
倒数: 6
倒数: 5
倒数: 4
倒数: 3
倒数: 2
倒数: 1

可以通过 next() 来一步步地让生成器 yield 一个值,直到函数迭代器结束并抛出 StopIteration。如果你对这一头雾水,建议阅读《Fluent Python》14.4 章。
这里 for 其实相当于不断地调用 next 并处理 StopIteration

c = countdown(3)

# next(c)
3
# next(c)
2
# next(c)
1
把生成器当作管道

你可以嵌套生成器,这会导致类似于 Unix 命令行管道的效果

def add_A(seq):
    for item in seq:
        yield item + "-A"

def add_B(seq):
    for item in seq:
        yield item + "-B"

def add_C(seq):
    for item in seq:
        yield item + "-C"
        
seq = ["apple", "banana", "orange"]

stacked_generator = add_C(add_B(add_A(seq)))

for item in stacked_generator:
    print(item)
apple-A-B-C
banana-A-B-C
orange-A-B-C

可以看到,我们为 seq 里的每项都依次添加了一个 tag。

yield 可以接受传值

yield 的作用是向调用者返回一个值,调用者其实也可以向生成器传值。

def receiver():
    while True:
        received_item = yield
        print("收到:", received_item)

def caller():
    recv = receiver()
    next(recv) # 使生成器前进到 yield
    for i in "abcd":
        recv.send(i)
        
caller()
收到: a
收到: b
收到: c
收到: d

send 函数的返回值是什么呢?

def receiver():
    call_times = 0
    while True:
        item = yield call_times
        print("收到:", item)
        call_times += 1
        
def caller():
    recv = receiver()
    next(recv)
    for i in "abcd":
        ret_value = recv.send(i)
        print("返回值: ", ret_value)
        
caller()
收到: a
返回值:  1
收到: b
返回值:  2
收到: c
返回值:  3
收到: d
返回值:  4

所以 send 可以向生成器传值的同时会让生成器前进到下一个 yield 语句,并将 yield 右侧的值作为返回值。

生成器 101

yield 用于定义生成器函数

只要 yield 存在该函数必定是一个生成器

调用该函数返回一个生成器

让一个生成器前进

使用 next 使一个生成器前进到下一个 yield 语句处,并将产出值(yielded value)作为其返回值。使用 gen.__next__()效果相同。

注意:这是一个新创建的生成器唯一允许的操作。

def generator():
    yield "a"
    yield "b"
    
gen = generator()

# next(gen)
"a"
# next(gen)
"b"
给生成器传值

可以通过调用生成器的 send 方法来向生成器传值,这将让生成器从上次暂停的 yield 前进到下个 yield,并将产出值作为 send 的返回值。

def generator():
    item = yield "a"
    print(item)
    another_item = yield "b"
    
gen = generator()
print(next(gen))
print(gen.send(1))
a
1
b
关闭一个生成器

通过调用生成器 close 方法可以生成器在 yield 语句处抛出 GeneratorExit。这时仅允许 return,如果没有捕捉这个错误,生成器会静默关闭,不抛出错误。

def generator():
    times = 0
    while True:
        yield times
        times += 1
            
gen = generator()
print(next(gen))
print(next(gen))
gen.close() # 不会抛出错误
0
1
抛出错误

调用生成器的 throw 方法可以在 yield 处抛出某个特定类型的错误,如果生成器内部可以捕捉这个错误,那生成器将前进到下个 yield 语句处,并将产出值作为 throw 的返回值,否则中止生成器。
throw 的函数签名如下:

throw(typ, [,val, [,tb]])

其中 tyb 是某错误类型,val是错误信息,tb 为 traceback。更多信息可以参考官方的PEP0342

def generator():
    try:
        yield "apple"
    except RuntimeError as e:
        print("捕捉到:", e)
    yield "banana"
    

gen = generator()
print(next(gen))
print(gen.throw(RuntimeError, "运行错误"))
apple
捕捉到: 运行错误
banana
生成器返回值

如果在生成器函数中加上 return 那在运行到 return 时将会把返回值作为 StopIteration 的值传递出去。这个是 Python3 的特性,Python2 生成器不能返回某个值。

def generator():
    yield
    return "apple"
    
g = generator()
next(g)
try:
    next(g)
except StopIteration as e:
    print(e)
apple
生成器委托

使用 yield from 可以帮你对一个生成器不断调用 next 函数,并返回生成器的返回值。言下之意是你可以在生成器里调用生成器

def generator():
    yield "a"
    yield "b"
    return "c"
    
def func():
    result = yield from generator()
    print(result)

调用 func 结果是返回一个生成器

# func()


# next(func())
"a"

另外一个例子

def chain(x, y):
    yield from x
    yield from y
    
a = [1, 2, 3]
b = [4, 5, 6]

for i in chain(a, b):
    print(i, end=" ")
1 2 3 4 5 6 
c = [7, 8, 9]
for i in chain(a, chain(b, c)):
    print(i, end=" ")
1 2 3 4 5 6 7 8 9 
Part 1总结 生成器定义
def generator():
    ...
    yield
    ...
    return result
生成器操作
gen = generator()

# 使一个生成器前进
next(gen)

# 传递一个值
gen.send(item)

# 中止生成器
gen.close()

# 抛出错误
gen.throw(exc, val, tb)

# 委托
result = yield from gen

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/41954.html

相关文章

  • 成器进化协程 Part 2

    摘要:一个典型的上下文管理器类如下处理异常正如方法名明确告诉我们的,方法负责进入上下的准备工作,如果有需要可以返回一个值,这个值将会被赋值给中的。总结都是关于上下文管理器的内容,与协程关系不大。 Part 1 传送门 David Beazley 的博客 PPT 下载地址 在 Part 1 我们已经介绍了生成器的定义和生成器的操作,现在让我们开始使用生成器。Part 2 主要描述了如...

    fuyi501 评论0 收藏0
  • PHP回顾之协程

    摘要:本文先回顾生成器,然后过渡到协程编程。其作用主要体现在三个方面数据生成生产者,通过返回数据数据消费消费者,消费传来的数据实现协程。解决回调地狱的方式主要有两种和协程。重点应当关注控制权转让的时机,以及协程的运作方式。 转载请注明文章出处: https://tlanyan.me/php-review... PHP回顾系列目录 PHP基础 web请求 cookie web响应 sess...

    Java3y 评论0 收藏0
  • tornado 源码之 coroutine 分析

    摘要:源码之分析的协程原理分析版本为支持异步,实现了一个协程库。提供了回调函数注册当异步事件完成后,调用注册的回调中间结果保存结束结果返回等功能注册回调函数,当被解决时,改回调函数被调用。相当于唤醒已经处于状态的父协程,通过回调函数,再执行。 tornado 源码之 coroutine 分析 tornado 的协程原理分析 版本:4.3.0 为支持异步,tornado 实现了一个协程库。 ...

    NicolasHe 评论0 收藏0
  • PyTips 0x13 - Python 线程与协程(2)

    摘要:项目地址我之前翻译了协程原理这篇文章之后尝试用了模式下的协程进行异步开发,确实感受到协程所带来的好处至少是语法上的。 项目地址:https://git.io/pytips 我之前翻译了Python 3.5 协程原理这篇文章之后尝试用了 Tornado + Motor 模式下的协程进行异步开发,确实感受到协程所带来的好处(至少是语法上的:D)。至于协程的 async/await 语法是如...

    史占广 评论0 收藏0
  • 图文 视频双管齐下,带你全面彻底理解Retrofit源码,Android开发五年

    摘要:协程的判断条件下面我们来着重看下的源码,因为从这里开始就涉及到协程的判断。第二点是关键点,用来判断该方法的调用是否使用到了协程。原理我们先来看下使用协程是怎么写的这是一个标准的协程写法,然后我们再套用上面的条件,发现完全匹配不到。 第一眼看,跟我之前印象中的有点区别(也不知道是什么版本),return的时候居然...

    不知名网友 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<