python async await_Python async/await 手册

Python部落(www.freelycode.com)组织翻译, 禁止转载

译者注: async/await 是 python3.5 的新语法,本文中的代码需使用该版本或以上才能正确运行。

在过去几年内,异步编程由于某些好的原因得到了充分的重视。虽然它比线性编程难一点,但是效率相对来说也是更高。

比如,利用 Python 的异步协程 (async coroutine) ,在提交 HTTP 请求后,就没必要等待请求完成再进一步操作,而是可以一边等着请求完成,一边做着其他工作。这可能在逻辑上需要多些思考来保证程序正确运行,但是好处是可以利用更少的资源做更多的事。

即便逻辑上需要多些思考,但实际上在 Python 语言中,异步编程的语法和执行并不难。跟 Javascript 不一样,现在 Python 的异步协程已经执行得相当好了。

对于服务端编程,异步性似乎是 Node.js 流行的一大原因。我们写的很多代码,特别是那些诸如网站之类的高 I/O 应用,都依赖于外部资源。这可以是任何资源,包括从远程数据库调用到 POST 一个 REST 请求。一旦你请求这些资源的任一一个,你的代码在等待资源响应时便无事可做(译者注:如果没有异步编程的话)。

有了异步编程,在等待这些资源响应的过程中,你的代码便可以去处理其他的任务。

协程 (Coroutines)

在 Python 中,异步函数通常被称作协程,创建一个协程仅仅只需使用 async 关键字,或者使用 @asyncio.coroutine 装饰器。下面的任一代码,都可以作为协程工作,形式上也是等同的:

import asyncio

async def ping_server(ip):

pass

@asyncio.coroutine

def load_file(path):

pass

上面这俩特殊的函数,在调用时会返回协程对象。熟悉 JavaScript 中 Promise 的同学,可以把这个返回对象当作跟 Promise 差不多。调用他们中的任意一个,实际上并未立即运行,而是返回一个协程对象,然后将其传递到 Eventloop 中,之后再执行。

如果要判断一个函数是不是协程, asyncio 提供了 asyncio.iscoroutinefunction(func) 方法,如果要判断一个函数返回的是不是协程对象,则可以使用 asyncio.iscoroutine(obj) 。

Yield from

调用协程的方式有有很多, yield from 就是其中的一种。这种方式在 Python3.3 中被引入,在 Python3.5 中以 async/await 的形式进行了优化。yield from 表达式的使用方式如下:

import asyncio

@asyncio.coroutine

def get_jason(client, url):

file_content = yield from load_file('/Usrs/scott/data.txt')

正如所看到的, yield from 被使用在用 @asyncio.coroutine 装饰的函数内,如果想把 yield from 在这个函数外使用,将会抛出如下语法错误:

File "main.py", line 1

file_content = yield from load_file('/Users/scott/data.txt')

^

SyntaxError: 'yield' outside function

为了避免语法错误, yield from 必须在调用函数的内部使用(这个调用函数通常被装饰为协程)。

Async/await

较新的语法是使用 async/await 关键字。 async 从 Python3.5 开始被引进,跟 @asyncio.coroutine 装饰器一样,用来声明一个函数是一个协程。只要把它放在函数定义之前,就可以应用到函数上,使用方式如下:

async def ping_server(ip):

# ping code here...

实际调用这个函数时,使用 await 而不用 yield from ,当然,使用方式依然差不多:

async def ping_local(ip):

return await ping_server('192.168.1.1')

再强调一遍,跟 yield from 一样,不能在函数外部使用 await ,否则会抛出语法错误。 (译者注: async 用来声明一个函数是协程,然后使用 await调用这个协程, await 必须在函数内部,这个函数通常也被声明为另一个协程)

Python3.5 对这两种调用协程的方法都提供了支持,但是推荐 async/await 作为首选。

Event Loop

如果你还不知道如何开始和操作一个 Eventloop ,那么上诉有关协程所说的都起不了多大作用。 Eventloop 在执行异步函数时非常重要,重要到只要执行协程,基本上就得利用 Eventloop 。

Eventloop 提供了相当多的功能:注册,执行和取消延迟调用(异步函数)

创建客户端与服务端传输用于通信

创建子程序和通道跟其他的程序进行通信

指定函数的调用到线程池

Eventloop 有相当多的配置和类型可供使用,但大部分程序只需要如下方式预定函数即可:

import asyncio

async def speak_async():

print('OMG asynchronicity!')

loop = asyncio.get_event_loop()

loop.run_until_complete(speak_async())

loop.close()

有意思的是代码中的最后三行,首先获取默认的 Eventloop ( asyncio.get_event_loop() ),然后预定和运行异步任务,并在完成后结束循环。

loop.run_until_complete() 函数实际上是阻塞性的,也就是在所有异步方法完成之前,它是不会返回的。但因为我们只在一个线程中运行这段代码,它没法再进一步扩展,即使循环仍在运行。

可能你现在还没觉得这有多大的用处,因为我们通过调用其他 IO 来结束 Eventloop 中的阻塞(译者注:也就是在阻塞时进行其他 IO ),但是想象一下,如果在网页服务器上,把整个程序都封装在异步函数内,到时就可以同时运行多个异步请求了。

也可以将 Eventloop 的线程中断,利用它去处理所有耗时较长的 IO 请求,而主线程处理程序逻辑或者用户界面。

一个案例

让我们实际操作一个稍大的案例。下面这段代码就是一个非常漂亮的异步程序,它先从 Reddit 抓取 JSON 数据,解析它,然后打印出当天来自 /r/python,/r/programming 和 /r/compsci 的置顶帖。

所示的第一个方法 get_json() ,由 get_reddit_top() 调用,然后只创建一个 GET 请求到适当的网址。当这个方法和 await 一起调用后, Eventloop 便能够继续为其他的协程服务,同时等待 HTTP 响应达到。一旦响应完成, JSON 数据就返回到 get_reddit_top() ,得到解析并打印出来。

async def get_reddit_top(subreddit, client):

data1 = await get_json(client, 'https://www.reddit.com/r/' + subreddit + '/top.json?sort=top&t=day&limit=5')

j = json.loads(data1.decode('utf-8'))

for i in j['data']['children']:

score = i['data']['score']

title = i['data']['title']

link = i['data']['url']

print(str(score) + ': ' + title + ' (' + link + ')')

print('DONE:', subreddit + '\n')

def signal_handler(signal, frame):

loop.stop()

client.close()

sys.exit(0)signal.signal(signal.SIGINT, signal_handler)

asyncio.ensure_future(get_reddit_top('python', client))

asyncio.ensure_future(get_reddit_top('programming', client))

asyncio.ensure_future(get_reddit_top('compsci', client))

loop.run_forever()

这跟我们之前展示出来的代码略有不同。为了达到在 Eventloop 中运行多重协程的目的,我们使用 asyncio.ensure_future() ,然后运行无限循环以处理一切。

为了保证运行成功,你必须先安装 aiohttp ,可以使用 PIP 安装:pip install aiohttp

现在,只要保证你运行的 Python3.5 或者更高版本,你就可以获得如下输出:

$ python main.py

46: Python async/await Tutorial (http://stackabuse.com/python-async-await-tutorial/)

16: Using game theory (and Python) to explain the dilemma of exchanging gifts. Turns out: giving a gift probably feels better than receiving

56: Which version of Python do you use? (This is a poll to compare the popularity of Python 2 vs. Python 3) (http://strawpoll.me/6299023

DONE: python

71: The Semantics of Version Control - Wouter Swierstra (http://www.staff.science.uu.nl/~swier004/Talks/vc-semantics-15.pdf)

25: Favorite non-textbook CS books (https://www.reddit.com/r/compsci/comments/3xag9e/favorite_nontextbook_cs_books/)

13: CompSci Weekend SuperThread (December 18, 2015) (https://www.reddit.com/r/compsci/comments/3xacch/compsci_weekend_superthread_december_18

DONE: compsci

1752: 684.8 TB of data is up for grabs due to publicly exposed MongoDB databases (https://blog.shodan.io/its-still-the-data-stupid/)

773: Instagram's Million Dollar Bug? (http://exfiltrated.com/research-Instagram-RCE.php)

387: Amazingly simple explanation of Diffie-Hellman. His channel has tons of amazing videos and only a few views :( thought I would share! (h

DONE: programming

注意,如果多次运行这段代码,打印出来的 subreddit 数据在顺序上会有些许变化。这是因为每当我们调用一次代码都会释放对线程的控制,容许线程去处理另一个 HTTP 调用。这将导致谁先获得响应,谁就先打印出来。

结论

即使 Python 内置的异步操作没有 Javascript 那么顺畅,但这并不意味着就不能用它来把应用变得更有趣、更有效率。只要花半个小时的时间去了解它的来龙去脉,你就会感觉把异步操作应用到你的程序中将会是多美好的一件事。

英文原文: http://stackabuse.com/python-async-await-tutorial/

译者: Itwonders

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值