09 协程

1. 参考资料

资料
- https://blog.csdn.net/andybegin/article/details/77884645
- http://python.jobbole.com/86481/
- http://python.jobbole.com/87310/
- https://segmentfault.com/a/1190000009781688

2. 迭代器

2.1 定义
可迭代对象(Iterable):直接作用于for循环的变量
迭代器(Iterator):不但可以作用于for循环,还可以被next调用
- list是典型的可迭代对象,但不是迭代器
## 可迭代
l = [ i for i in range(10)]

# l是可迭代的,但不是迭代器
for idx in l:
    print(idx)

# range是个迭代器
for i in range(5):
    print(i)
通过isinstance判断是可迭代对象、是否可迭代
iterable和iterator可以通过iter函数转换
- 语法:isinstance(iter('abc'), Iterator)
# isinstance--判断某个变量是否是一个实例

# 判断是否课可迭代
from collections import Iterable
from collections import Iterator
l = [1,2,3,4,5]

# 判断是否是可迭代对象
print(isinstance(l, Iterable))
# 判断是否可迭代
print(isinstance(l, Iterator))

ll = iter(l)
print(isinstance(ll, Iterator))

>True
 False
 True

3. 生成器

3.1 定义
生成器(generator):一边循环一边计算的机制/算法
需要满足三个条件:
    每次调用都生产出for循环需要的下一个元素
    如果达到最后一个,爆出StopIteration异常
    可以被next函数调用
   
生成器的典型用法是在for中使用,典型生成器就是range
3.2 生成器创建
直接使用
## 直接使用生成器
L = [x * x for x in range(10)] # 放在中括号中是列表生成器
g = (x * x for x in range(10)) # 放在小括号中是生成器

print(type(L))
print(type(g))

><class 'list'>
 <class 'generator'>

创建生成器
- 函数中包含yield,则叫generator
- next调用,遇到yield返回
# 生成器案例--函数

def odd():
    print("Step 1")
    yield 1
  print("Step 2")
    yield 2
  print("Step 3")
    yield 3
  return None

s0 = odd()

s1 = next(s0)
print(s1)
s2 = next(s0)
print(s2)
s3 = next(s0)
print(s3)

>Step 1
 1
 Step 2
 2
 Step 3
 3

# for循环调用生成器
def fib(max):
    n, a, b = 0, 0, 1 # 注意写法
  while n < max:
        print(b)
        a, b = b, a + b  # 注意写法
  n += 1

  return 'Done'

fib(5)


>1
 1
 2
 3
 5
# 斐波那契额数列的生成器写法
def fib(max):
    n, a, b = 0, 0, 1 # 注意写法
    while n < max:
        yield b
        a, b = b, a + b  # 注意写法
  n += 1

  # 需要注意,爆出异常是的返回值是return的返回值
  return 'Done'

g = fib(5)

for i in range(6):
    rst = next(g)
    print(rst)
   
>1
 1
 2
 3
 5
 .....
 StopIteration: Done

4. 协程

4.1 定义
定义:协程 是为非抢占式多任务产生子程序的计算机程序组件,协程允许不同入口点在不同位置暂停或开始执行程序”。
- 从技术角度讲,协程就是一个你可以暂停执行的函数,或者干脆把协程理解成生成器

历史历程
- 3.4引入协程,用yield实现
- 3.5引入协程语法

协程的实现:
- yield返回
- send调用
- 比较好的包有asyncio, tornado, gevent
def simple_coroutine():
    print('转到协程')
    x = yield ## 返回到主进程
    print('协程接收到主进程值x:{0}'.format(x))

sc = simple_coroutine()
print('开始主进程')

# 可以使用sc.send(None),效果一样
next(sc)  ## 预激

print('yield--回到主进程')
sc.send('zhexiao')

>开始主进程
 转到协程
 yield--回到主进程
 协程接收到主进程值x:zhexiao
 StopIteration
4.2 协程的四个状态
inspect.getgeneratorstate(…) 函数确定,该函数会返回下述字符串中的一个:
- GEN_CREATED:等待开始执行
- GEN_RUNNING:解释器正在执行
- GEN_SUSPENED:在yield表达式处暂停
- GEN_CLOSED:执行结束
- next预激(prime)

协程终止
- 协程中未处理的异常会向上冒泡,传给 next 函数或 send 方法的调用方(即触发协程的对象)
- 止协程的一种方式:发送某个哨符值,让协程退出。内置的 None 和Ellipsis 等常量经常用作哨符值==。

异常

- 客户段代码可以在生成器对象上调用两个方法
    generator.throw(Exctpiton):
    - 致使生成器在暂停的 yield 表达式处抛出指定的异常。如果生成器处理了抛出的异常,代码会向前执行到下一个 yield 表达式,而产出的值会成为调用 generator.throw方法得到的返回值。如果生成器没有处理抛出的异常,异常会向上冒泡,传到调用方的上下文中。

    generator.close()
    - 致使生成器在暂停的 yield 表达式处抛出 GeneratorExit 异常。
    - 如果生成器没有处理这个异常,或者抛出了 StopIteration 异常(通常是指运行到结尾),
    - 调用方不会报错。如果收到 GeneratorExit 异常,生成器一定不能产出值,否则解释器会抛出RuntimeError 异常。生成器抛出的其他异常会向上冒泡,传给调用方。
class DemoException(Exception):
    """
 custom exception """  pass

def handle_exception():
    print('-> start')

    while True:
        try:
            x = yield
        except DemoException:
            print('-> run demo exception')
        else:
            print('-> recived x:', x)

    raise RuntimeError('this line should never run')

he = handle_exception()
next(he)
he.send(10) # recived x: 10
he.send(20) # recived x: 20

he.throw(DemoException) # run demo exception

he.send(40) # recived x: 40
he.close()

>-> start
 -> recived x: 10
 -> recived x: 20
 -> run demo exception
 -> recived x: 40

yield from
- 调用协程为了得到返回值,协程必须正常终止
- 生成器正常终止会发出StopIteration异常,异常对象的vlaue属性保存返回值
- yield from从内部捕获StopIteration异常

- 委派生成器
    - 包含yield from表达式的生成器函数
    - 委派生成器在yield from表达式处暂停,调用方可以直接把数据发给自生成器
    - 子生成器在把产出的值发给调用方
    - 子生成器在最后,解释器会抛出StopIteration,并且把返回值附加到异常对象上
## return from


def gen():
    for c in 'AB':
        yield c

## list直接用生成器作为参数
print(list(gen()))

def gen_new():
    yield from 'AB'

print(list(gen_new()))

>['A', 'B']
 ['A', 'B']
# 委派生成器
from collections import namedtuple

'''
解释:
1. 外层 for 循环每次迭代会新建一个 grouper 实例,赋值给 coroutine 变量; grouper 是委派生成器。
2. 调用 next(coroutine),预激委派生成器 grouper,此时进入 while True 循环,调用子生成器 averager 后,在 yield from 表达式处暂停。
3. 内层 for 循环调用 coroutine.send(value),直接把值传给子生成器 averager。同时,当前的 grouper 实例(coroutine)在 yield from 表达式处暂停。
4. 内层循环结束后, grouper 实例依旧在 yield from 表达式处暂停,因此, grouper函数定义体中为 results[key] 赋值的语句还没有执行。
5. coroutine.send(None) 终止 averager 子生成器,子生成器抛出 StopIteration 异常并将返回的数据包含在异常对象的value中,yield from 可以直接抓取 StopItration 异常并将异常对象的 value 赋值给 results[key]
'''
ResClass = namedtuple('Res', 'count average')

# 子生成器
def averager():
    total = 0.0
    count = 0
    average = None

    while True:
        term = yield
        # None是哨兵值
        if term is None:
            break
        total += term
        count += 1
        average = total / count

    return ResClass(count, average)

# 委派生成器
def grouper(storages, key):
    while True:
        # 获取averager()返回的值
        storages[key] = yield from averager()

# 客户端代码
def client():
    process_data = {
        'boys_2': [39.0, 40.8, 43.2, 40.8, 43.1, 38.6, 41.4, 40.6, 36.3],
        'boys_1': [1.38, 1.5, 1.32, 1.25, 1.37, 1.48, 1.25, 1.49, 1.46]
    }

    storages = {}
    for k, v in process_data.items():
        # 获得协程
        coroutine = grouper(storages, k)

        # 预激协程
        next(coroutine)

        # 发送数据到协程
        for dt in v:
            coroutine.send(dt)

        # 终止协程
        coroutine.send(None)
    print(storages)

# run
client()

>{'boys_2': Res(count=9, average=40.422222222222224), 'boys_1': Res(count=9, average=1.3888888888888888)}

5. 异步

5.1 asyncio
python3.4开始引入的标准库,内置了对异步io的支持
asyncio本身是一个消息循环
步骤:
    创建消息循环
    把协程导入
    关闭
import asyncio

@asyncio.coroutine
def hello():
    print("Hello world!")
    # 异步调用asyncio.sleep(1):
    print("Start......")
    r = yield from asyncio.sleep(3)
    print("Done....")
    print("Hello again!")

# 获取EventLoop:
loop = asyncio.get_event_loop()
# 执行coroutine
loop.run_until_complete(hello())
loop.close()

>Hello world!
 Start......
 Done....
 Hello again!
案例:得到多个网站
import asyncio

@asyncio.coroutine
def wget(host):
    print('wget %s...' % host)
    connect = asyncio.open_connection(host, 80)
    reader, writer = yield from connect
    header = 'GET / HTTP/1.0\r\nHost: %s\r\n\r\n' % host
    writer.write(header.encode('utf-8'))
    yield from writer.drain()
    while True:
        line = yield from reader.readline()
        if line == b'\r\n':
            break
    print('%s header > %s' % (host, line.decode('utf-8').rstrip()))
    # Ignore the body, close the socket
    writer.close()

loop = asyncio.get_event_loop()
tasks = [wget(host) for host in ['www.sina.com.cn', 'www.sohu.com', 'www.163.com']]
loop.run_until_complete(asyncio.wait(tasks))
loop.close()

>
wget www.163.com...
wget www.sina.com.cn...
wget www.sohu.com...
www.163.com header > HTTP/1.0 302 Moved Temporarily
www.163.com header > Server: Cdn Cache Server V2.0
www.163.com header > Date: Sun, 26 Aug 2018 13:44:32 GMT
www.163.com header > Content-Length: 0
www.163.com header > Location: http://www.163.com/special/0077jt/error_isp.html
www.163.com header > Connection: close
www.sina.com.cn header > HTTP/1.1 302 Moved Temporarily
www.sina.com.cn header > Server: nginx
www.sina.com.cn header > Date: Sun, 26 Aug 2018 13:44:32 GMT
www.sina.com.cn header > Content-Type: text/html
www.sina.com.cn header > Content-Length: 154
www.sina.com.cn header > Connection: close
www.sina.com.cn header > Location: https://www.sina.com.cn/
www.sina.com.cn header > X-Via-CDN: f=edge,s=ctc.wuhan.ha2ts4.30.nb.sinaedge.com,c=59.174.49.231;
www.sina.com.cn header > X-Via-Edge: 1535291072741e731ae3b7e84af3b477882f5
www.sohu.com header > HTTP/1.1 200 OK
www.sohu.com header > Content-Type: text/html;charset=UTF-8
www.sohu.com header > Connection: close
www.sohu.com header > Server: nginx
www.sohu.com header > Date: Sun, 26 Aug 2018 13:43:44 GMT
www.sohu.com header > Cache-Control: max-age=60
www.sohu.com header > X-From-Sohu: X-SRC-Cached
www.sohu.com header > Content-Encoding: gzip
www.sohu.com header > FSS-Cache: HIT from 9449353.16461715.11224052
www.sohu.com header > FSS-Proxy: Powered by 3813171.5189437.5587784

5.2 async and await
为了更好的表示异步io
python3.5 开始引入
让coroutine代码更简洁
使用上,可以简单进行替换:
    - 可以把 @asyncio.coroutine 替换成async
    - yield from 替换成await
import threading
import asyncio

#@asyncio.coroutine
async  def hello():
    print('Hello world! (%s)' % threading.currentThread())
    print('Start..... (%s)' % threading.currentThread())
    await asyncio.sleep(10)
    print('Done..... (%s)' % threading.currentThread())
    print('Hello again! (%s)' % threading.currentThread())

loop = asyncio.get_event_loop()
tasks = [hello(), hello()]
loop.run_until_complete(asyncio.wait(tasks))
loop.close()

>略
5.3 aiohttp
asyncio实现单线程并发IO,在客户端用处不大
在服务器端可以asyncio+coroutine配合,因为http是io操作
asyncio实现了TCP,UIDP,SSL等协议
aiohttp是给予asyncio实现的HTTP框架
import asyncio

from aiohttp import web

async def index(request):
    await asyncio.sleep(0.5)
    return web.Response(body=b'<h1>Index</h1>')

async def hello(request):
    await asyncio.sleep(0.5)
    text = '<h1>hello, %s!</h1>' % request.match_info['name']
    return web.Response(body=text.encode('utf-8'))

async def init(loop):
    app = web.Application(loop=loop)
    app.router.add_route('GET', '/', index)
    app.router.add_route('GET', '/hello/{name}', hello)
    srv = await loop.create_server(app.make_handler(), '127.0.0.1', 8000)
    print('Server started at http://127.0.0.1:8000...')
    return srv

loop = asyncio.get_event_loop()
loop.run_until_complete(init(loop))
loop.run_forever()
5.4 concurrent.futures
类似其他语言的线程池的概念,此模块利用multiprocessiong实现真正的平行计算
核心原理是:concurrent.futures会以子进程的形式,平行的运行多个python解释器,从而令python程序可以利用多核CPU来提升执行速度。
- 由于子进程与主解释器相分离,所以他们的全局解释器锁也是相互独立的。每个子进程都能够完整的使用一个CPU内核。

concurrent.futures.Executor
    - ThreadPoolExecutor
    - ProcessPoolExecutor
submit(fn, args, kwargs)
    - fn:异步执行的函数
    - args,kwargs:参数
# 官方死锁案例
import time
def wait_on_b():
    time.sleep(5)
    print(b.result())  #b不会完成,他一直在等待a的return结果
    return 5

def wait_on_a():
    time.sleep(5)
    print(a.result())  #同理a也不会完成,他也是在等待b的结果
    return 6


executor = ThreadPoolExecutor(max_workers=2)
a = executor.submit(wait_on_b)
b = executor.submit(wait_on_a)
map(fn, \*iterables, timeout=None)
- 跟map函数类似
- 函数需要异步执行
- timeout: 超时时间
- 起执行结果是list,数据需要从list中取出来
- submit和map根据需要选一个即可
import time,re
import os,datetime
from concurrent import futures

data = ['1','2']

def wait_on(argument):
   print(argument)
   time.sleep(2)
   return "ok"

ex = futures.ThreadPoolExecutor(max_workers=2)
for i in ex.map(wait_on,data):
   print(i)
 Future
- 未来需要完成的任务
- future 实例由Excutor.submit创建
from concurrent.futures import ThreadPoolExecutor as Pool
from concurrent.futures import as_completed
import requests

URLS = ['http://qq.com', 'http://sina.com', 'http://www.baidu.com', ]

def task(url, timeout=10):
    return requests.get(url, timeout=timeout)

with Pool(max_workers=3) as executor:
    future_tasks = [executor.submit(task, url) for url in URLS]

    for f in future_tasks:
        if f.running():
            print('%s is running' % str(f))

    for f in as_completed(future_tasks):
        try:
            ret = f.done()
            if ret:
                f_ret = f.result()
                print('%s, done, result: %s, %s' % (str(f), f_ret.url, len(f_ret.content)))
        except Exception as e:
            f.cancel()
            print(str(e))

猜你喜欢

转载自blog.csdn.net/qq_25672165/article/details/89068270