python3 gui协程_Python3 协程(coroutine)介绍

目前 Python 语言的协程从实现来说可分为两类:一种是基于传统生成器的协程,叫做 generator-based coroutines,通过包装 generator 对象实现。

另一种在 Python 3.5 版本 PEP 492 诞生,叫做 native coroutines,即通过使用 async 语法来声明的协程。

本文主要介绍第二种,第一种基于生成器的协程已在 Python 3.8 中弃用,并计划在 Python 3.10 中移除。本文是「介绍」,就先不讨论太多实现原理的东西,感兴趣的童鞋可以继续关注后面的文章。

协程(coroutine)

首先,来看一个非常简单的例子:

import asyncio

async def c():

await asyncio.sleep(1)

return 'Done '

这个被 async 修饰的函数 c 就是一个协程函数,该函数会返回一个协程对象。

In [1]: asyncio.iscoroutinefunction(c)

Out[1]: True

In [2]: c()

Out[2]:

In [3]: asyncio.iscoroutine(c())

Out[3]: True

一般来说,协程函数 c 应具有以下特点:一定会返回一个协程对象,而不管其中是否有 await 表达式。

函数中不能再使用 yield from。

函数内部可通过 await 表达式来挂起自身协程,并等待另一个协程完成直到返回结果。

await 表达式后面可以跟的一定是一个可等待对象,而不仅仅是协程对象。

不可在 async 修饰的协程函数外使用 await 关键字,否则会引发一个 SyntaxError。

当对协程对象进行垃圾收集时,如果从未等待过它,则会引发一个 RuntimeWarning(如果你刚开始写 async,那你一定遇得到,不经意间就会忘掉一个 await)。

下面分别介绍下上面提到的两个概念,可等待对象和协程对象。

可等待对象(awaitable)

我们来看 collections.abc 模块中对 Awaitable 类的定义:

class Awaitable(metaclass=ABCMeta):

__slots__ = ()

@abstractmethod

def __await__(self):

yield

@classmethod

def __subclasshook__(cls, C):

if cls is Awaitable:

return _check_methods(C, "__await__")

return NotImplemented

可见,可等待对象主要实现了一个 __await__ 方法。且该方法必须返回一个迭代器(iterator) ①,否则将会引发一个 TypeError。注意:主要实现是因为 await 表达式需要跟老的基于生成器的协程相兼容,即通过使用 types.coroutine() 或 asyncio.coroutine() 装饰器返回的生成器迭代器对象(generator iterator)也属于可等待对象,但它们并未实现 __await__ 方法。

协程对象(Coroutine)

同样的,我们来看 collections.abc 模块中对 Coroutine 类的定义:

class Coroutine(Awaitable):

__slots__ = ()

@abstractmethod

def send(self, value):

"""Send a value into the coroutine.Return next yielded value or raise StopIteration."""

raise StopIteration

@abstractmethod

def throw(self, typ, val=None, tb=None):

"""Raise an exception in the coroutine.Return next yielded value or raise StopIteration."""

if val is None:

if tb is None:

raise typ

val = typ()

if tb is not None:

val = val.with_traceback(tb)

raise val

def close(self):

"""Raise GeneratorExit inside coroutine."""

try:

self.throw(GeneratorExit)

except (GeneratorExit, StopIteration):

pass

else:

raise RuntimeError("coroutine ignored GeneratorExit")

@classmethod

def __subclasshook__(cls, C):

if cls is Coroutine:

return _check_methods(C, '__await__', 'send', 'throw', 'close')

return NotImplemented

由上可知,由于继承关系,协程对象是属于可等待对象的。

除了协程 Coroutine 对象外,目前常见的可等待对象还有两种:asyncio.Task 和 asyncio.Future,下文中介绍。

协程的执行可通过调用 __await__() 并迭代其结果进行控制。当协程结束执行并返回时,迭代器会引发 StopIteration 异常,并通过该异常的 value 属性来传播协程的返回值。下面看一个简单的例子:

In [4]: c().send(None)

Out[4]:

In [5]: async def c1():

...: return "Done "

...:

In [6]: c1().send(None)

Out[6]: StopIteration: Done

运行

协程的运行需要在一个 EventLoop 中进行,由它来控制异步任务的注册、执行、取消等。其大致原理是:把传入的所有异步对象(准确的说是可等待对象,如 Coroutine,Task 等,见下文)都注册到这个 EventLoop 上,EventLoop 会循环执行这些异步对象,但同时只执行一个,当执行到某个对象时,如果它正在等待其他对象(I/O 处理) 返回,事件循环会暂停它的执行去执行其他的对象。当某个对象完成 I/O 处理后,下次循环到它的时候会获取其返回值然后继续向下执行。这样以来,所有的异步任务就可以协同运行。

EventLoop 接受的对象必须为可等待对象,目前主要有三种类型即 Coroutine, Task 和 Future。

下面简单的介绍下 Task 和 Future:Future 是一种特殊的低级的可等待对象,用来支持底层回调式代码与高层 async/await 式的代码交互,是对协程底层实现的封装,其表示一个异步操作的最终结果。它提供了设置和获取 Future 执行状态或结果等操作的接口。Future 实现了 __await__ 协议,并通过 __iter__ = __await__ 来兼容老式协程。一般来说,我们不需要关心这玩意儿,日常的开发也是不需要要用到它的。如有需要,就用其子类 Task。

Task 用来协同的调度协程以实现并发,并提供了相应的接口供我们使用。

创建一个 Task 非常简单:

In [6]: loop = asyncio.get_event_loop()

In [7]: task = loop.create_task(c())

In [8]: task

Out[8]: :3>>

In [9]: task.done()

Out[9]: False

In [10]: task.cancelled()

Out[10]: False

In [11]: task.result()

Out[11]: InvalidStateError: Result is not set.

In [12]: await task

Out[12]: 'Done '

In [13]: task

Out[13]: :3> result='Done '>

In [14]: task.done()

Out[14]: True

In [15]: task.result()

Out[15]: 'Done '

In [16]: task = loop.create_task(c())

In [17]: task.cancel()

Out[17]: True

In [18]: await task

Out[18]: CancelledError:

上面说到,协程的运行需要在一个 EventLoop 中进行,在 Python 3.7 之前,你只能这么写 :

In [19]: loop = asyncio.get_event_loop()

In [20]: loop.run_until_complete(c())

Out[20]: 'Done '

In [21]: loop.close()

Python 3.7 及以上版本可以直接使用 asyncio.run() :

In [22]: asyncio.run(c())

Out[22]: 'Done '

并发

有些童鞋可能有疑问了,我写好了一个个协程函数,怎样才能并发的运行 ?

asyncio 提供了相应的两个接口:asyncio.gather 和 asyncio.wait 来支持:

async def c1():

await asyncio.sleep(1)

print('c1 done')

return True

async def c2():

await asyncio.sleep(2)

print('c2 done')

return True

async def c12_by_gather():

await asyncio.gather(c1(), c2())

async def c12_by_awit():

await asyncio.wait([c1(), c2()])

In [23]: asyncio.run(c12_by_gather())

c1 done

c2 done

In [24]: asyncio.run(c12_by_awit())

c1 done

c2 done

其它

上面我们介绍了 PEP 492 coroutine 的基础使用,同时 PEP 492 也相应提出了基于 async with 和 async for 表达式的异步上下文管理器(asynchronous context manager)和异步迭代器(asynchronous iterator)。

下面的介绍的示例将基于Python 可迭代对象, 迭代器和生成器里的示例展开,建议感兴趣的同学可以先看下这篇文章。

异步上下文管理器

在 Python 中,我们常会通过实现 __enter__() 和 __exit__() 方法来实现一个上下文管理器:

In [1]: class ContextManager:

...:

...: def __enter__(self):

...: print('enter...')

...:

...: def __exit__(slef, exc_type, exc_val, exc_tb):

...: print('exit...')

...:

In [2]: with ContextManager():

...: print('Do something...')

...:

enter...

Do something...

exit...

同样的,在异步编程时我们可以通过实现 __aenter__() 和 __aexit__() 方法来实现一个上下文管理器,并通过 async with 表达式来使用。

In [1]: class AsyncContextManager:

...:

...: async def __aenter__(self):

...: print('async enter...')

...:

...: async def __aexit__(slef, exc_type, exc_val, exc_tb):

...: print('async exit...')

...:

In [2]: async with AsyncContextManager():

...: print('Do something...')

...:

async enter...

Do something...

async exit...

异步迭代

在之前的文章 Python 可迭代对象, 迭代器和生成器 中,我们介绍了通过实现 __iter__() 方法来实现一个可迭代对象,通过实现迭代器协议 __iter__() 和 __next__() 方法来实现一个迭代器对象。下面我们改造下之前的例子,实现一个异步的版本。

class AsyncLinkFinder:

PATTERN = "(?<=href=\").+?(?=\")|(?<=href=\').+?(?=\')"

def __init__(self, text):

self.links = re.findall(self.PATTERN, text)

def __aiter__(self):

return AsyncLinkIiterator(self.links)

class AsyncLinkIiterator:

def __init__(self, links):

self.links = links

self.index = 0

async def _gen_link(self):

try:

link = self.links[self.index]

self.index += 1

except IndexError:

link = None

return link

def __aiter__(self):

return self

async def __anext__(self):

link = await self._gen_link()

if link is None:

raise StopAsyncIteration

return link

In [7]: async for s in AsyncLinkFinder(TEXT):

...: print(s)

https://blog.python.org

http://feedproxy.google.com/~r/PythonSoftwareFoundationNew/~3/T3r7qZxo-xg/python-software-foundation-fellow.html

http://feedproxy.google.com/~r/PythonSoftwareFoundationNews/~3/lE0u-5MIUQc/why-sponsor-pycon-2020.html

http://feedproxy.google.com/~r/PythonSoftwareFoundationNews/~3/jAMRqiPhWSs/seeking-developers-for-paid-contract.html

例子中实现了 __aiter__() 方法的 AsyncLinkFinder 就是一个异步可迭代对象,__aiter__() 方法返回的必须是一个异步迭代器,如 AsyncLinkIiterator。异步迭代器必须同时实现 __aiter__() 和 __anext__() 方法。一个不同的点是,异步迭代中,当迭代器耗尽时,需要引发一个 StopAsyncIteration 而不是 StopIteration。

同样的,我们也实现一个异步生成器版本的:

class LinkFinder:

PATTERN = "(?<=href=\").+?(?=\")|(?<=href=\').+?(?=\')"

def __init__(self, text):

self.links = re.finditer(self.PATTERN, text)

async def __aiter__(self):

return (link.group() for link in self.links)

参考

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值