生成器进阶

2018 年 10 月 12 日 • 阅读数: 111

生成器进阶

C10M问题

  • 如何利用8核心CPU,64G内存,在10gbps的网络上保持1000万并发链接

在使用协程之前我们现有的问题

  • 同步编程的并发性不高
  • 回调模式编码复杂度高
  • 多线程编程需要线程间同步,引入lock的机制,大大降低了性能
  • 线程间切换代价大

使用协程的目的

  • 采用同步的方式去编写异步的代码
  • 使用单线程去切换任务
  • 线程是由操作系统切换的,单线程切换意味着我们需要自己去调度任务
  • 单线程下不需要锁,在单线程内切换函数,性能远高于线程切换,并发行更高

send方法

  • 通过send方法可以向生成器内传值(注意生成器最开始只能接收一个None,或者先调用一次next方法)
def gen_func():
    html = yield 'http://projectsedu.com'
    print(html)
    html = yield 2
    print(html)
    yield 3
    return 4
gen = gen_func()
next(gen)
'http://projectsedu.com'
gen.send(None)
None





2
gen.send('http://baidu.com')
http://baidu.com





3
gen.send('http://QQ.com')
http://QQ.com





2

close方法

  • 可以通过close方法,关闭一个生成器,关闭之后再调用next将抛出异常
def gen_func():
    yield 1
    try:
        yield 2
    except Exception:
        pass
    yield 3
    return 4
gen = gen_func()
next(gen)
gen.close()
next(gen)
---------------------------------------------------------------------------

StopIteration                             Traceback (most recent call last)

<ipython-input-100-ed0364b04088> in <module>()
      1 next(gen)
      2 gen.close()
----> 3 next(gen)


StopIteration: 

throw方法

  • 可以通过throw来向生成器内部抛一个异常
  • 可以通过在内部捕获异常来处理它
print(next(gen))
print(next(gen))
gen.throw(Exception, 'error')
try:
    next(gen)
except Exception as e:
    print(e)
1
2
4

yield from

def gen_func1(iterable):
    yield iterable
def gen_func2(iterable):
    yield from iterable
[value for value in gen_func1(range(10))]
[range(0, 10)]
[value for value in gen_func2(range(10))]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
  • 在下面的例子中 main 是调用方,middle 是委托生成器,sales_sum 是子生成器
  • yield from 会在调用方与子生成器之间建立一个双向通道
final_result = {}

def sales_sum(pro_name):
    total = 0
    nums = []
    while True:
        x = yield
        print(pro_name + '销量:', x)
        if not x:
            break
        total += x
        nums.append(x)
    return total, nums

def middle(key):
    while True:
        final_result[key] = yield from sales_sum(key)
        print(key + '销量统计完成')
        
def main():
    data_sets = {
        'sony': [1200, 1500, 3000],
        'apple': [28, 55, 98, 108],
        'xiaomi': [280, 560, 778, 70]
    }
    for key, data_set in data_sets.items():
        print('start key:', key)
        m = middle(key)
        m.send(None)
        for value in data_set:
            m.send(value)
        m.send(None)
    print('final_result:', final_result)
main()
start key: sony
sony销量: 1200
sony销量: 1500
sony销量: 3000
sony销量: None
sony销量统计完成
start key: apple
apple销量: 28
apple销量: 55
apple销量: 98
apple销量: 108
apple销量: None
apple销量统计完成
start key: xiaomi
xiaomi销量: 280
xiaomi销量: 560
xiaomi销量: 778
xiaomi销量: 70
xiaomi销量: None
xiaomi销量统计完成
final_result: {'sony': (5700, [1200, 1500, 3000]), 'apple': (289, [28, 55, 98, 108]), 'xiaomi': (1688, [280, 560, 778, 70])}
  • 子生成器生产的值,都是直接传递给调用方,调用方通过send()发送的值都是直接发送给子生成器
  • 如果发送的是None,会调用子生成器的__next__()方法,如果不是None,会调用子生成器的send()方法
  • 子生成器退出的时候,最后return EXPR ,会触发一个StopIteration(EXPR)异常
  • yield from 表达式的值,是子生成器终止时,传递给StopIteration异常的第一个参数
  • 如果调用的时候出现StopIteration异常,委托生成器会恢复运行,同时其他异常会向上“冒泡”
  • 传入委托生成器的异常里,除了GeneratorExit之外,其他的异常全部传递给子生成器的 throw()方法
  • 如果调用 throw()的时候出现了StopIteration异常,那么就恢复委托生成器的运行,其他异常全部向上“冒泡”
  • 如果在委托生成器上调用 close() 或传入 GeneratorExit 异常,会调用子生成器的 close()方法
  • 没有的话就不调用,如果调用close()的时候抛出异常,那么就向上“冒泡”,否则委托生成器会抛出GeneratorExit异常

使用 async 和 await 实现原生的协程

async def downloader(url):
    return 'amor'

async def download_url(url):
    html = await downloader(url)
    return html
coro = download_url('http://www.baidu.com')
coro.send(None)
---------------------------------------------------------------------------

StopIteration                             Traceback (most recent call last)

<ipython-input-28-3562bb68222d> in <module>()
      1 coro = download_url('http://www.baidu.com')
----> 2 coro.send(None)


StopIteration: amor
标签: Python生成器

召唤伊斯特瓦尔