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 表达式的生成器函数
子生成器
从 yield from 部分获取的生成器, 含义 yield 的.
调用方
调用委派生成器的客户端 (调用方) 代码, 也就是运行入口.
ok, 了解了这些我们看接下来的一个例子.
使用 yeild from 写一个异步爬虫
- import requests
- from collections import namedtuple 1
- Response = namedtuple("rs", 'url status') 2
- # 子生产器
- def fecth(): 3
- res=[]
- while 1:
- url = yield 4
- if url is None: 5
- break
- req = requests.get(url)
- res.append(Response(url=url, status=req.status_code))
- return res
- # 委派生成器
- def url_list(l, key):
- while 1: 6
- l[key] = yield from fecth() 7
- # 调用方
- 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) 8
- ul.send(url)9
- ul.send(None)10
- return l
- if __name__ == '__main__':
- res = main()
- print(res)
接下来对上面的标准进行解释:
1 引入一个具名元组, 可以后面实现一个简单的类.
2 对请求参数做一个格式化处理, 后面通过获取属性即可.
3一个协程, 通过 requests 模块可以发起网络请求.
4main 函数的发送的值绑定到这里的 url 上
5 url 为 None 即没有 url 的时候结束循环的.
6这个循环每次都会新建一个 fetch 实例, 每个实例都是作为协程使用的生成器对象.
7 url_list 发送的每个值都会经由 yield from 处理, 然后传给 fetch 实例. url_list 会在 yield from 表达式处暂停, 等待 fetch 实例处理客户端发来的值. fetch 实例运行完毕后, 返回的值绑定到 l[key] 上. while 循环会不断创建 fetch 实例, 处理更多的值.
8激活 url_list 生成器
9把各个 url 以及其序列号 index, 传给 url_list 传入的值最终到达 fetch 函数中, url_list 并不知道传入的是什么, 同时 url_list 实例在 yield from 处暂停. 直到 fetch 的一个实例处理完才进行赋值.
10关键的一步,# 把 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
来源: https://juejin.im/post/5c1062d95188254caf18902d