# 异步编程之使用yield from

1,768

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链接可迭代对象

``````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]
``````

### 扁平化处理嵌套型的数据

``````# 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在看接下来的代码之前我们必须知道这几个概念：

#### 调用方

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的一个实例处理完才进行赋值。
⑩关键的一步，# 把None传入url_list，传入的值最终到达fetch函数中，导致当前实例终止。然后继续创建下一个实例。如果没有ul.send(None)，那么fetch子生成器永远不会终止，因为ul.send()发送的值实际是在fetch实例中进行，委派生成器也永远不会在此激活，也就不会为l[key]赋值

### 参考资料：

PEP 380-- Syntax for Delegating to a Subgenerator
How Python 3.3 "yield from" construct works