美文网首页Python
Python Async/Await

Python Async/Await

作者: hehehehe | 来源:发表于2021-09-08 15:43 被阅读0次

https://zhuanlan.zhihu.com/p/27258289
https://docs.python.org/zh-cn/3/library/asyncio-task.html#running-an-asyncio-program

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()

@asyncio.coroutine把一个generator标记为coroutine类型,然后,我们就把这个coroutine扔到EventLoop中执行。

hello()会首先打印出Hello world!,然后,yield from语法可以让我们方便地调用另一个generator。由于asyncio.sleep()
也是一个coroutine,所以线程不会等待asyncio.sleep(),而是直接中断并执行下一个消息循环。当asyncio.sleep()返回时,
线程就可以从yield from拿到返回值(此处是None),然后接着执行下一行语句。

把asyncio.sleep(1)看成是一个耗时1秒的IO操作,在此期间,主线程并未等待,
而是去执行EventLoop中其他可以执行的coroutine了,因此可以实现并发执行。

可迭代、迭代器、生成器

可迭代的对象,很好理解,我们很熟悉的:字符串,list,dict,tuple,deque等

import collections
from collections.abc import Iterable, Iterator, Generator

# 字符串
astr = "XiaoMing"
print("字符串:{}".format(astr))
print(isinstance(astr, Iterable))
print(isinstance(astr, Iterator))
print(isinstance(astr, Generator))

字符串:XiaoMing
True
False
False

这些可迭代对象都不是迭代器,也不是生成器。它们有一个共同点,就是它们都可以使用for来循环
可迭代对象,是其内部实现了,iter 这个魔术方法。
可以通过,dir()方法来查看是否有iter来判断一个变量是否是可迭代的。
迭代器其实就只是多了一个函数而已。就是next(),我们可以不再使用for循环来间断获取元素值。而可以直接使用next()方法来实现。

from collections.abc import Iterator

aStr = 'abcd'  # 创建字符串,它是可迭代对象
aIterator = iter(aStr)  # 通过iter(),将可迭代对象转换为一个迭代器
print(isinstance(aIterator, Iterator))  # True
next(aIterator)  # a
next(aIterator)  # b
next(aIterator)  # c
next(aIterator)  # d

迭代器,是其内部实现了,next 这个魔术方法。(Python3.x)
可以通过,dir()方法来查看是否有next来判断一个变量是否是迭代器的。

迭代器,是在可迭代的基础上,加了一个next()方法。
生成器,则是在迭代器的基础上(可以用for循环,可以使用next()),再实现了yield
yield 是什么东西呢,它相当于我们函数里的return。在每次next(),或者for遍历的时候,都会yield这里将新的值返回回去,并在这里阻塞,等待下一次的调用。正是由于这个机制,才使用生成器在Python编程中大放异彩。实现节省内存,实现异步编程。

如何运行/激活生成器

激活主要有两个方法

  • 使用next()
  • 使用generator.send(None)
def mygen(n):
    now = 0
    while now < n:
        yield now
        now += 1

if __name__ == '__main__':
    gen = mygen(4)

    # 通过交替执行,来说明这两种方法是等价的。
    print(gen.send(None))
    print(next(gen))
    print(gen.send(None))
    print(next(gen))
生成器的执行状态
  • GEN_CREATED # 等待开始执行
  • GEN_RUNNING # 解释器正在执行(只有在多线程应用中才能看到这个状态)
  • GEN_SUSPENDED # 在yield表达式处暂停
  • GEN_CLOSED # 执行结束
yield from关键字

yield from 将一个可迭代对象变成一个迭代器返回,也可以说,yield from关键字可以直接返回一个生成器

>>> # yield from 将一个可迭代对象变成一个迭代器返回
>>> def func2():
    lst = ['str', 'tuple', 'list', 'dict', 'set']
    yield from lst

>>> gen2 = func2()
>>> next(gen2)
'str'
>>> next(gen2)
'tuple'
>>> for i in gen2:
    print(i)
从生成器过渡到协程:yield

我们知道生成器为我们引入了暂停函数执行(yield)的功能。当有了暂停的功能之后,人们就想能不能在生成器暂停的时候向其发送一点东西(其实上面也有提及:send(None))
协程通过使用 yield 暂停生成器,可以将程序的执行流程交给其他的子程序,从而实现不同子程序的之间的交替执行

In [142]: def test():
     ...:     i=0
     ...:     while i<5:
     ...:         t = yield i
     ...:         print(t,i)
     ...:         i+=1
In [148]: a=test()

In [149]: a.send(None)
Out[149]: 0

In [150]: a.send("34")
34 0
Out[150]: 1
* yield i 是将i return给外部调用程序。
* t = yield 可以接收外部程序通过send()发送的信息,并赋值给t

为什么python的yield第一次不能用send发送数据?
https://www.zhihu.com/question/28105502
因为最开始,gen()函数运行到yield i时,是从gen()把i的值抛回给调用者所以最开始send进去一个具体值,是几个意思?顺序不对啊!!现在人家并不是回到gen()生成器,而是第一次运行gen()函数时人家要从gen()那里出来啊send(var)是发生在回到gen()时的操作,而不是从gen()出来时的操作,这是需要明确差别的一点

因为协程是一个线程执行,那怎么利用多核CPU呢?
最简单的方法是多进程+协程,既充分利用多核,又充分发挥协程的高效率,可获得极高的性能。
Python对协程的支持是通过generator实现的。

asyncio模块

https://www.cnblogs.com/51try-again/p/11074621.html
asyncio是Python 3.4版本引入的标准库,直接内置了对异步IO的支持。
用asyncio提供的@asyncio.coroutine可以把一个generator标记为coroutine类型
然后在coroutine内部用yield from调用另一个coroutine实现异步操作。
asyncio的编程模型就是一个消息循环。我们从asyncio模块中直接获取一个EventLoop的引用,然后把需要执行的协程扔到EventLoop中执行,就实现了异步IO。

coroutine+yield from

import asyncio
 
@asyncio.coroutine
def hello():
    print("Nice to learn asyncio.coroutine!")
    # 异步调用asyncio.sleep(1):
    r = yield from asyncio.sleep(1)
    print("Nice to learn asyncio.coroutine again !")
 
 
# 获取EventLoop:
loop = asyncio.get_event_loop()
# 执行coroutine
loop.run_until_complete(hello())
loop.close()

Nice to learn asyncio.coroutine !
Nice to learn asyncio.coroutine again !

为了简化并更好地标识异步IO,从Python 3.5开始引入了新的语法async和await,可以让coroutine的代码更简洁易读。
 请注意,async和 await是针对coroutine的新语法,要使用新的语法,只需要做两步简单的替换:

  • 把@asyncio.coroutine替换为async;
  • 把yield from替换为await。

执行多个任务

import threading
import asyncio
 
 
async def hello():
    print('Hello Python! (%s)' % threading.currentThread())
    await asyncio.sleep(1)
    print('Hello Python again! (%s)' % threading.currentThread())
 
loop = asyncio.get_event_loop()
tasks = [hello(), hello()]
loop.run_until_complete(asyncio.wait(tasks))
loop.close()

Hello Python! (<_MainThread(MainThread, started 4536)>)
Hello Python! (<_MainThread(MainThread, started 4536)>)
Hello Python again! (<_MainThread(MainThread, started 4536)>)
Hello Python again! (<_MainThread(MainThread, started 4536)>)

执行多个任务获取返回值

import threading
import asyncio
 
async def hello(seq):
    print('Hello Python! (%s)' % threading.currentThread())
    await asyncio.sleep(1)
    print('Hello Python again! (%s)' % threading.currentThread())
    return "It's done", seq
 
loop = asyncio.get_event_loop()
task1 = loop.create_task(hello(2))
task2 = loop.create_task(hello(1))
task_list = [task1, task2]
tasks = asyncio.wait(task_list)
loop.run_until_complete(tasks)
 
for t in task_list:
    print(t.result())

可等待 对象有三种主要类型: 协程, 任务 和 Future.

  • 协程函数: 定义形式为 async def 的函数;

  • 协程对象: 调用 协程函数 所返回的对象。

import asyncio

async def nested():
    return 42

async def main():
    # Schedule nested() to run soon concurrently
    # with "main()".
    task = asyncio.create_task(nested())

    # "task" can now be used to cancel "nested()", or
    # can simply be awaited to wait until it is complete:
    await task

asyncio.run(main())

相关文章

网友评论

    本文标题:Python Async/Await

    本文链接:https://www.haomeiwen.com/subject/lbhfeltx.html