生成器与协程

说明

本文来自https://blog.csdn.net/weixin_30748995/article/details/98896747,感谢大佬演示了从生成器到协程转变的过程,希望对大家有所帮助。

定义生成器

yield关键字,可以让我们定义一个生成器函数。

def generator_func():
    print('a')
    yield 1

g = generator_func()
print(g)

>> > < generator object generator_func at 0x10e178b88 >

推动生成器

使用next函数从生成器中取值

def generator_func():
    print('a')
    yield 1


g = generator_func()
ret1 = next(g)
print(ret1)

>> >
a
1

使用next可以推动生成器的执行,下面的代码,我们可以看到每一次执行next可以让generator_func中的代码从上一个位置开始继续执行到yield,并且将yield后面的值返回到函数外部,最终我们可以执行到yield

def generator_func():
    print('a')
    yield 1
    print('b')
    yield 2
    print('c')
    yield 3
    print('d')


g = generator_func()
ret1 = next(g)
print(ret1)
ret2 = next(g)
print(ret2)
ret3 = next(g)
print(ret3)

>> >
a
1
b
2
c
3

当函数中已经没有更多的yield时继续执行next(g),遇到StopIteration

def generator_func():
    print('a')
    yield 1
    print('b')
    yield 2
    print('c')
    yield 3
    print('d')


g = generator_func()
ret1 = next(g)
print(ret1)
ret2 = next(g)
print(ret2)
ret3 = next(g)
print(ret3)
next(g)

next和StopIteration

send向生成器中发送数据。send的作用相当于next,只是在驱动生成器继续执行的同时还可以向生成器中传递数据。

import numbers


def cal_sum():
    sum_num = 0
    while True:
        num = yield
        if isinstance(num, numbers.Integral):
            sum_num += num
            print('sum :', sum_num)
        elif num is None:
            break


g = cal_sum()
g.send(None)  # 相当于next(g),预激活生成器
g.send(31)
g.send(25)
g.send(17)
g.send(8)

>> >
sum: 31
sum: 56
sum: 73
sum: 81

生成器中的return和StopIteration

import numbers

def cal_sum():
    sum_num = 0
    while True:
        num = yield
        if isinstance(num, numbers.Integral):
            sum_num += num
            print('sum :', sum_num)
        elif num is None:
            break
    return sum_num


g = cal_sum()
g.send(None)  # 相当于next(g),预激活生成器
g.send(31)
g.send(25)
g.send(17)
g.send(8)
g.send(None)  # 停止生成器

>> >
sum: 31
sum: 56
sum: 73
sum: 81
Traceback(most recent call last):
  File  "/Users/jingliyang/PycharmProjects/python的进阶/manager.py", line 19, in < module >
  g.send(None)
  StopIteration: 81
import numbers

def cal_sum():
    sum_num = 0
    while True:
        num = yield
        if isinstance(num, numbers.Integral):
            sum_num += num
            print('sum :', sum_num)
        elif num is None:
            break
    return sum_num


g = cal_sum()
g.send(None)  # 相当于next(g),预激活生成器
g.send(31)
g.send(25)
g.send(17)
g.send(8)
try:
    g.send(None)  # 停止生成器
except StopIteration as e:
    print(e.value)

异常处理以及获取return的值

生成器的close和throw

使用throw向生成器中抛一个异常,throw和send、next相同,都是驱动生成器继续执行,只不过throw用来向生成器中抛一个异常

def throw_test():
    print('a')
    yield 1
    print('b')
    yield 2


g = throw_test()
next(g)
g.throw(Exception, 'value error')

>> >
a Traceback(most recent call last):
  File "/Users/jingliyang/PycharmProjects/python的进阶/manager.py", line 32, in < module >
g.throw(ValueError, 'value error')  
  File "/Users/jingliyang/PycharmProjects/python的进阶/manager.py", line 26, in throw_test 
  yield 1 
  ValueError: value
error
def throw_test():
    print('a')
    try:
        yield 1
    except ValueError:
        pass
    print('b')
    yield 2


g = throw_test()
next(g)
ret = g.throw(ValueError, 'value error') 
print(ret)

>> >
a
b
2

throw + 异常处理

使用close关闭一个生成器

def throw_test():
    print('a')
    yield 1
    print('b')
    yield 2


g = throw_test()
ret1 = next(g)
print(ret1)
g.close()
next(g)

>> >
a
1
Traceback(most recent call last):
  File "/Users/jingliyang/PycharmProjects/python的进阶/manager.py", line 45, in < module >
  next(g)
StopIteration

yield from关键字

yield from关键字可以直接返回一个生成器

l = ['h', 'e', 'l']
dic = {'l': 'v1', 'o': 'v2'}
s = 'eva'


def yield_from_gen():
    for i in l:
        yield i
    for j in dic:
        yield j
    for k in s:
        yield k


for item in yield_from_gen():
    print(item, end='++')

>> > h++e++l++l++o++e++v++a++
l = ['h', 'e', 'l']
dic = {'l': 'v1', 'o': 'v2'}
s = 'eva'


def yield_from_gen():
    yield from l
    yield from dic
    yield from s


for item in yield_from_gen():
    print(item, end='++')

>> > h++e++l++l++o++e++v++a++
from itertools import chain

l = ['h', 'e', 'l']
dic = {'l': 'v1', 'o': 'v2'}
s = 'eva'


def yield_from_gen():
    yield from chain(l, dic, s)


for item in yield_from_gen():
    print(item, end='++')

>> > h++e++l++l++o++e++v++a++

chain和yield from

利用yield from完成股票的计算,yield from能够完成一个委派生成器的作用,在子生成器和调用者之间建立起一个双向通道。

from itertools import chain

l = ['h', 'e', 'l']
dic = {'l': 'v1', 'o': 'v2'}
s = 'eva'


def yield_from_gen():
    yield from chain(l, dic, s)


for item in yield_from_gen():
    print(item, end='++')

"""
0 -
6.4 -
6.45 -
6.5 -
6.425 -
6.359999999999999 -
6.3999999999999995 -
6.442857142857143 -
sogou 6.442857142857143
0 -
0 -
181.72 -
183.15 -
183.28 -
182.46 -
163.568 -
164.62 -
166.5614285714286 -
169.40625 -
alibaba 169.40625
0 -
0 -
59.7 -
56.150000000000006 -
53.166666666666664 -
53.725 -
55.120000000000005 -
56.95000000000001 -
59.38571428571429 -
美团 59.38571428571429
0 -

(test2) C:\Users\asinall4\PycharmProjects\test2>python 083.py
0 -
6.4 -
6.45 -
6.5 -
6.425 -
6.359999999999999 -
6.3999999999999995 -
6.442857142857143 -
++++++++++++++++++++++++++++++++++++++++++++++++++
sogou 6.442857142857143
0 -
0 -
181.72 -
183.15 -
183.28 -
182.46 -
163.568 -
164.62 -
166.5614285714286 -
169.40625 -
++++++++++++++++++++++++++++++++++++++++++++++++++
alibaba 169.40625
0 -
0 -
59.7 -
56.150000000000006 -
53.166666666666664 -
53.725 -
55.120000000000005 -
56.95000000000001 -
59.38571428571429 -
++++++++++++++++++++++++++++++++++++++++++++++++++
美团 59.38571428571429
0 -

"""

协程

概念:根据维基百科给出的定义,“协程是为非抢占式多任务产生子程序的计算机程序组件,协程允许不同入口点在不同位置暂停或开始执行程序”。从技术的角度来说,“协程就是你可以暂停执行的函数”。如果你把它理解成“就像生成器一样”,那么你就想对了。

使用yield实现协程

import time

def consumer():
    '''任务1:接收数据,处理数据'''
    while True:
        x = yield


def producer():
    '''任务2:生产数据'''
    g = consumer()
    next(g)
    for i in range(10000000):
        g.send(i)


producer()

使用yield from实现的协程

import datetime
import heapq
import types
import time


class Task:
    def __init__(self, wait_until, coro):
        self.coro = coro
        self.waiting_until = wait_until

    def __eq__(self, other):
        return self.waiting_until == other.waiting_until

    def __lt__(self, other):
        return self.waiting_until < other.waiting_until


class SleepingLoop:

    def __init__(self, *coros):
        self._new = coros
        self._waiting = []

    def run_until_complete(self):
        for coro in self._new:
            wait_for = coro.send(None)
            heapq.heappush(self._waiting, Task(wait_for, coro))
        while self._waiting:
            now = datetime.datetime.now()
            task = heapq.heappop(self._waiting)
            if now < task.waiting_until:
                delta = task.waiting_until - now
                time.sleep(delta.total_seconds())
                now = datetime.datetime.now()
            try:
                print('*' * 50)
                wait_until = task.coro.send(now)
                print('-' * 50)
                heapq.heappush(self._waiting, Task(wait_until, task.coro))
            except StopIteration:
                pass


def sleep(seconds):
    now = datetime.datetime.now()
    wait_until = now + datetime.timedelta(seconds=seconds)
    print('before yield wait_until')
    actual = yield wait_until  # 返回一个datetime数据类型的时间
    print('after yield wait_until')
    return actual - now


def countdown(label, length, *, delay=0):
    print(label, 'waiting', delay, 'seconds before starting countdown')
    delta = yield from sleep(delay)
    print(label, 'starting after waiting', delta)
    while length:
        print(label, 'T-minus', length)
        waited = yield from sleep(1)
        length -= 1
    print(label, 'lift-off!')


def main():
    loop = SleepingLoop(countdown('A', 5), countdown('B', 3, delay=2),
                        countdown('C', 4, delay=1))
    start = datetime.datetime.now()
    loop.run_until_complete()
    print('Total elapsed time is', datetime.datetime.now() - start)


if __name__ == '__main__':
    main()

await和async关键字

使用async function可以定义一个异步函数,在async关键字定义的函数中不能出现yield和yield from

例1

async def download(url):  # 加入新的关键字 async ,可以将任何一个普通函数变成协程
    return 'eva'

ret = download('http://www.baidu.com/')
print(ret)  # <coroutine object download at 0x108b3a5c8>
ret.send(None)  # StopIteration: eva

例2

async def download(url):
    return 'eva'


def run(coroutine):
    try:
        coroutine.send(None)
    except StopIteration as e:
        return e.value


coro = download('http://www.baidu.com/')
ret = run(coro)
print(ret)  # eva

async关键字不能和yield一起使用,引入coroutine装饰器来装饰downloader生成器。
await操作符后面必须跟一个awaitable对象(通常用于等待一个会有io操作的任务), 它只能在异步函数async function内部使用。

例3

import types


@types.coroutine  # 将一个生成器变成一个awaitable的对象
def downloader(url):
    yield 'aaa'


async def download_url(url):  # 协程
    waitable = downloader(url)
    print(waitable)  # <generator object downloader at 0x1091e2c78>     生成器
    html = await waitable
    return html


coro = download_url('http://www.baidu.com')
print(coro)  # <coroutine object download_url at 0x1091c9d48>
ret = coro.send(None)
print(ret)

asyncio模块

asyncio是Python3.4版本引入的标准库,直接内置了对异步IO的支持。asyncio的编程模型就是一个消息循环。我们从asyncio模块中直接获取一个EventLoop的引用,然后把需要执行的协程扔到EventLoop中执行,就实现了异步IO。

coroutine + from
import asyncio

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


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

async + await

import asyncio

async def hello():
    print("Hello world!")
    # 异步调用asyncio.sleep(1):
    r = await asyncio.sleep(1)
    print("Hello again!")


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

执行多个任务

import asyncio

async def hello():
    print("Hello world!")
    await asyncio.sleep(1)
    print("Hello again!")
    return 'done'


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

获取返回值

import asyncio

async def hello():
    print("Hello world!")
    await asyncio.sleep(1)
    print("Hello again!")
    return 'done'


loop = asyncio.get_event_loop()
task = loop.create_task(hello())
loop.run_until_complete(task)
ret = task.result()
print(ret)

执行多个任务获取返回值

import asyncio

async def hello(i):
    print("Hello world!")
    await asyncio.sleep(i)
    print("Hello again!")
    return 'done', i


loop = asyncio.get_event_loop()
task1 = loop.create_task(hello(2))
task2 = loop.create_task(hello(1))
task_l = [task1, task2]
tasks = asyncio.wait(task_l)
loop.run_until_complete(tasks)
for t in task_l:
    print(t.result())

执行多个任务按照返回的顺序获取返回值

import asyncio

async def hello(i):
    print("Hello world!")
    await asyncio.sleep(i)
    print("Hello again!")
    return 'done', i

async def main():
    tasks = []
    for i in range(20):
        tasks.append(asyncio.ensure_future(hello((20 - i) / 10)))
    for res in asyncio.as_completed(tasks):
        result = await res
        print(result)


loop = asyncio.get_event_loop()
loop.run_until_complete(main())

asyncio使用协程完成http访问

import asyncio

async def get_url():
    reader, writer = await asyncio.open_connection('www.baidu.com', 80)
    writer.write(b'GET / HTTP/1.1\r\nHOST:www.baidu.com\r\nConnection:close\r\n\r\n')
    all_lines = []
    async for line in reader:
        data = line.decode()
        all_lines.append(data)
    html = '\n'.join(all_lines)
    return html

async def main():
    tasks = []
    for url in range(20):
        tasks.append(asyncio.ensure_future(get_url()))
    for res in asyncio.as_completed(tasks):
        result = await res
        print(result)


if __name__ == '__main__':
    loop = asyncio.get_event_loop()
    loop.run_until_complete(main())  # 处理一个任务
    loop.run_until_complete(asyncio.wait([main()]))  # 处理多个任务

    task = loop.create_task(main())  # 使用create_task获取返回值
    loop.run_until_complete(task)
    loop.run_until_complete(asyncio.wait([task]))
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,294评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,780评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,001评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,593评论 1 289
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,687评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,679评论 1 294
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,667评论 3 415
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,426评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,872评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,180评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,346评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,019评论 5 340
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,658评论 3 323
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,268评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,495评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,275评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,207评论 2 352

推荐阅读更多精彩内容