V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
推荐学习书目
Learn Python the Hard Way
Python Sites
PyPI - Python Package Index
http://diveintopython.org/toc/index.html
Pocoo
值得关注的项目
PyPy
Celery
Jinja2
Read the Docs
gevent
pyenv
virtualenv
Stackless Python
Beautiful Soup
结巴中文分词
Green Unicorn
Sentry
Shovel
Pyflakes
pytest
Python 编程
pep8 Checker
Styles
PEP 8
Google Python Style Guide
Code Style from The Hitchhiker's Guide
cxa
V2EX  ›  Python

异步编程之使用 yield from

  •  
  •   cxa · 2018-12-12 09:20:00 +08:00 · 2186 次点击
    这是一个创建于 2177 天前的主题,其中的信息可能已经有所发展或是发生改变。

    异步编程之使用 yield from

    yield from 是 Python3.3 后新加的语言结构。yield from 的主要功能是打开双向通道,把最外层的调用方法与最内层的子生成器连接起来。这两者就可以进行发送值和返回值了,yeild from 结构的本质是简化嵌套的生产器,不理解这个是什么意思的话,下面我将用几个例子来对其使用方法进行讲解。

    简化 for 循环中的 yeild

    首先看一个

    def gene():
        for c in 'AB':
            yield c  #遇到 yeild 程序返回循环,下次从 yeild 后面开始。
        for i in range(3):
            yield i 
    if __name__=="__main__":
        list(gene())#list 内部会预激生成器
    

    输出

    ['A','B','0','1', '2']
    

    上面的代码可以简写成

    def gene():
         yield from 'ab' 
         yield from range(3)
    if __name__=="__main__":
        list(gene()) 
    

    通过上面的代码我们可以知道,yield from 可以简化 for 循环里的 yield 表达式。当然 yeild from 的功能不仅仅是可以简化 for 循环而已,要是这样的话也就不值得,单独写一篇文章来介绍了。

    我们仔细观察,简化后的式子有两个 yeild from,同样的也就是说如果有 10 个 for 循环的 yeild 生成式,我们需要写 10 个 yeild from,此时我们要记得在 python 中如果重复的代码出现了两次以及以上就该考虑优化了。好了接下来我们看一个优化后的例子。

    通过 yield from 链接可迭代对象

    def chain(*args):
        for i in args:
            # for m in i:
            #  yield m
            yield from i
    p = list(chain("1234", "AB", [1, 2, 3, 4, 5]))
    print(p)
    

    输出

    ['1', '2', '3', '4', 'A', 'B', 1, 2, 3, 4, 5]
    

    这里对之前的例子做了个优化处理,通过*args 可变参数,配合后面的 for 循环进行了多个可迭代对象的链接处理。下面来看一个复杂点的例子:(来自 Python cookbook 3,github 源码地址 https://github.com/dabeaz/python-cookbook/blob/master/src/4/how_to_flatten_a_nested_sequence/example.py)

    扁平化处理嵌套型的数据

    # Example of flattening a nested sequence using subgenerators
    
    from collections import Iterable
    
    def flatten(items, ignore_types=(str, bytes)):
        for x in items:
            if isinstance(x, Iterable) and not isinstance(x, ignore_types):
                yield from flatten(x)
            else:
                yield x
    
    items = [1, 2, [3, 4, [5, 6], 7], 8]
    
    # Produces 1 2 3 4 5 6 7 8
    for x in flatten(items):
        print(x)
    
    items = ['Dave', 'Paula', ['Thomas', 'Lewis']]
    for x in flatten(items):
        print(x)
    

    接下来通过说一下开篇提到的子生产器和调用方以及新的词委托生成器。

    了解几个概念

    yield from x 表达式对 x 对象做的第一件事是,调用 iter(x),从中获取一个迭代器。所以 x 是可迭代对象。上面的例子中的 x 如果是可迭代对象就会执行,yield from flatten(x).

    PEP380 的标题是 ” syntax for delegating to subgenerator “(把指责委托给子生成.器的句法)。由此我们可以知道,yield from 是可以实现嵌套生成器的使用。

    yield from 在看接下来的代码之前我们必须知道这几个概念:

    委派生成器

    包含 yield from <iterable> 表达式的生成器函数</iterable>

    子生成器

    从 yield from <iterable> 部分获取的生成器,含义 yield 的。</iterable>

    调用方

    调用委派生成器的客户端(调用方)代码,也就是运行入口。

    ok,了解了这些我们看接下来的一个例子。

    使用 yeild from 写一个异步爬虫

    import requests
    from collections import namedtuple  ①
    
    Response = namedtuple("rs", 'url status') ②
    
    
    #子生产器
    def fecth(): ③
        res=[]
        while 1:
            url = yield ④
            if url is None: ⑤
                break
            req = requests.get(url)
            res.append(Response(url=url, status=req.status_code))
        return res
    
    #委派生成器
    def url_list(l, key):
        while 1: ⑥
            l[key] = yield from fecth() ⑦
    
    #调用方
    def main():
        l = {}
        u = ["http://www.baidu.com", "http://www.cnblogs.com"]
        for index, url in enumerate(u):
            if index == 0:
                ul = url_list(l, index)
                next(ul) ⑧
            ul.send(url)⑨
        ul.send(None)⑩
        return l
    
    
    if __name__ == '__main__':
        res = main()
        print(res)
    

    接下来对上面的标准进行解释: ① 引入一个具名元组,可以后面实现一个简单的类。 ② 对请求参数做一个格式化处理,后面通过获取属性即可。 ③一个协程,通过 requests 模块可以发起网络请求。 ④main 函数的发送的值绑定到这里的 url 上 ⑤ url 为 None 即没有 url 的时候结束循环的。 ⑥这个循环每次都会新建一个 fetch 实例,每个实例都是作为协程使用的生成器对象。 ⑦ url_list 发送的每个值都会经由 yield from 处理,然后传给 fetch 实例。url_list 会在 yield from 表达式处暂停,等待 fetch 实例处理客户端发来的值。fetch 实例运行完毕后,返回的值绑定到 l[key] 上。while 循环会不断创建 fetch 实例,处理更多的值。 ⑧激活 url_list 生成器 ⑨把各个 url 以及其序列号 index,传给 url_list 传入的值最终到达 fetch 函数中,url_list 并不知道传入的是什么,同时 url_list 实例在 yield from 处暂停。直到 fetch 的一个实例处理完才进行赋值。 ⑩关键的一步,ul 把 None 传入 url_list,传入的值最终到达 fetch 函数中,导致当前实例终止。然后继续创建下一个实例。如果没有 ul.send(None),那么 fetch 子生成器永远不会终止,因为 ul.send()发送的值实际是在 fetch 实例中进行,委派生成器也永远不会在此激活,也就不会为 l[key]赋值

    参考资料:

    流畅的 python 第 16 章 PEP 380-- Syntax for Delegating to a Subgenerator How Python 3.3 "yield from" construct works

    第 1 条附言  ·  2018-12-12 10:52:04 +08:00
    因为后续的 asyncio 是 yield from 演变过来 所以从根部说起,
    第 2 条附言  ·  2018-12-12 11:30:03 +08:00
    例子 1 修正:
    def gene():
    for c in 'AB':
    yield c #遇到 yeild 程序返回循环,下次从 yeild 后面开始。
    for i in range(3):
    yield i
    if __name__=="__main__":
    print(list(gene()))#list 内部会预激生成器
    输出

    ['A','B',0,1, 2]
    第 3 条附言  ·  2018-12-12 11:30:21 +08:00

    def gene(): for c in 'AB': yield c #遇到 yeild 程序返回循环,下次从 yeild 后面开始。 for i in range(3): yield i if name=="main": list(gene())#list 内部会预激生成器 输出

    ['A','B','0','1', '2']

    ericls
        1
    ericls  
       2018-12-12 09:22:45 +08:00 via iPhone
    好像要淘汰了
    Kilerd
        2
    Kilerd  
       2018-12-12 09:24:21 +08:00   ❤️ 1
    都 8102 年了,还用 yield ? async/ await 了解一下?
    meik2333
        3
    meik2333  
       2018-12-12 10:01:51 +08:00
    可是你这个异步爬虫不分明是同步的吗。。。。。。
    Vegetable
        4
    Vegetable  
       2018-12-12 10:05:44 +08:00
    说的好,异步呢?
    ClutchBear
        5
    ClutchBear  
       2018-12-12 10:07:05 +08:00
    用 requests 写,
    不管怎么弄都是同步的.
    需要用 aiohttp 才行.
    jadeity
        6
    jadeity  
       2018-12-12 10:18:14 +08:00
    为什么要用过渡方法不用 await ?
    mseasons
        7
    mseasons  
       2018-12-12 10:34:33 +08:00
    就很僵硬……幸好没公众号
    ballshapesdsd
        8
    ballshapesdsd  
       2018-12-12 10:37:27 +08:00
    兄弟你是不是忘发公众号了
    cxa
        9
    cxa  
    OP
       2018-12-12 10:52:47 +08:00
    @mseasons 啥僵硬
    cxa
        10
    cxa  
    OP
       2018-12-12 10:54:37 +08:00
    @ericls 没错已经淘汰了 但是我想在理解 asyncio 之前 从 yield from 的思想学起比较好 学的是一种思路
    cxa
        11
    cxa  
    OP
       2018-12-12 10:55:47 +08:00
    @ClutchBear 嗯嗯 这个例子主要是直到 yield from 的工作原理,后期会用 asyncio+aiohttp 等 异步例子,还在学习中。
    xpresslink
        12
    xpresslink  
       2018-12-12 11:06:43 +08:00
    楼主请稍认真点,目测第一个例子的输出 ['A','B','0','1', '2'] 是错的。
    locoz
        13
    locoz  
       2018-12-12 11:12:01 +08:00
    @xpresslink #12 hhhhhhhhh 明明根本连输出都不会有
    www5070504
        14
    www5070504  
       2018-12-12 11:21:55 +08:00
    看见贴长文的就不知道怎么说 感觉很难受
    congeec
        15
    congeec  
       2018-12-12 21:55:02 +08:00
    chain 那个函数能一行搞定,还没怎么这样写过呢
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   5406 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 30ms · UTC 09:17 · PVG 17:17 · LAX 01:17 · JFK 04:17
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.