【翻译】Python async/await Tutorial

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

过去几年,异步编程方式被越来越多的程序员使用, 当然这是有原因的。 尽管异步编程比顺序编程更难, 但是它也更高效。

在顺序编程中, 发起一个HTTP请求需要阻塞以等待他的返回结果, 使用异步编程你可以发起这个HTTP请求, 然后在等待结果返回的同时做一些其他的事情,等待结果的协程会被放在一个队列里面。 为了保证逻辑的正确性, 这可能会需要考虑的更多, 但是这也使我们用更少的资源处理更多的事情。 Python中的异步语法和调用并不难。 和Javascript的异步编程比起来可能有点难, 不过也还好。

异步的处理方式可能解释了为什么Node.js在服务器端这么流行。 我们的代码依赖于外部的一些资源, 尤其是IO密集型的应用, 比如网站可能需要从数据库调用中向一个REST接口POST数据。 一旦我们请求一些外部资源, 我们的代码就要阻塞, 而不能处理别的逻辑。

利用异步编程, 我们可以在等待其他资源返回的时候, 做一些其他的事情。

Coroutines

在python中,异步函数被称作协程: 使用async关键字 或者利用@asyncio.coroutine装饰器。 下面的两种形式是等效的:

import asyncio

async def ping_server(ip):  
    pass


@asyncio.coroutinedef load_file(path):  
    pass

上面的函数调用的时候返回的是一个协程的对象。 如果你熟悉javascript, 你可以认为这个返回对象就像javascript里面的Promise。 现在调用这两个函数, 是不能执行的, 仅仅返回的是一个协程对象, 这个对象可以被用来在后面的event loop中使用。

如果你想知道一个函数是不是协程, asyncio提供的asyncio.iscoroutine(obj)函数可以帮助你。

Yield from

有几种调用协程的方式,其中一种是使用yield from方法。 yield from在Python3.3中被引进, 在Python3.5的async/await(我们后面会提到) 得到进一步的扩展。 yield from表达式可以用如下的方式使用:

import asyncio@asyncio.coroutinedef get_json(client, url):  
    file_content = yield from load_file('/Users/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, 典型的用法是在有@asyncio.coroutine装饰器的函数种使用。

Async/await

更新、更方便的语法是使用async/await关键字。async关键字是在Python3.5引入的, 被用来修饰一个函数, 让其成为协程, 和@asyncio.coroutine功能类似。 使用如下:

async def ping_server(ip):  
    # ping code here...

调用这个函数, 使用await, 而不是yield from, 不过方式差不多:

async def ping_local():  
    return await ping_server('192.168.1.1')

你不能在一个协程外面使用await关键字, 否则会得到语法错误。 就像yield from不能在函数外面使用一样。

Python3.5中, 上面两种协程声明的方式都支持, 但是首选async/await方式。

Running the event loop

上面描述的协程例子都不会正常的运行, 如果要运行, 需要用到event loop.。event loop是协程执行的控制点, 如果你希望执行协程, 就需要用到它们。

event loop提供了如下的特性:

  • 注册、执行、取消延时调用(异步函数)
  • 创建用于通信的client和server协议(工具)
  • 创建和别的程序通信的子进程和协议(工具)
  • 把函数调用送入线程池中

有一些配置和event loop的类型你可以使用, 但是如果你想去执行一个函数, 可以使用下面的配置, 而且在大多数场景中这样就够了:

import asyncio

async def speak_async():  
    print('OMG asynchronicity!')

loop = asyncio.get_event_loop()  
loop.run_until_complete(speak_async())  
loop.close()  

最后三行是重点。 asyncio启动默认的event loop(asyncio.get_event_loop()), 调度并执行异步任务, 关闭event loop。

loop.run_until_complete()这个函数是阻塞执行的, 直到所有的异步函数执行完毕。 因为我们的程序是单线程运行的, 所以, 它没办法调度到别的线程执行。

你可能会认为这不是很有用, 因为我们的程序阻塞在event loop上(就像IO调用), 但是想象一下这样: 我们可以把我们的逻辑封装在异步函数中, 这样你就能同时执行很多的异步请求了, 比如在一个web服务器中。

你可以把event loop放在一个单独的线程中, 让它执行IO密集型的请求, 而主线程可以继续处理程序逻辑或者UI渲染。

An example

OK, 让我看一个稍微长一点的例子, 这个例子是可以实际运行的。 例子是一个简单的从Reddit的/r/python, /r/programming, and /r/compsci页面异步获取JSON数据, 解析, 打印出这些页面发表的文章。

get_json()方法是被get_reddit_top()调用的, get_reddit_top()发起了一个HTTP GET请求到Reddit。 当调用被await修饰, event loop就会继续在等待请求返回的时候处理其他的协程。 一旦请求返回, JSON数据会被返回get_reddit_top(), 然后解析, 打印。

import signal  import sys  import asyncio  import aiohttp  import json

loop = asyncio.get_event_loop()  
client = aiohttp.ClientSession(loop=loop)

async def get_json(client, url):  
    async with client.get(url) as response:        assert response.status == 200
        return await response.read()

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

这个程序和上面展示的例子有一点不同。 我们使用asyncio.ensure_future()让event loop处理多个协程, 然后让event loop一直执行, 直到处理了所有的请求。

为了执行这个程序, 需要安装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 one... (http://vknight.org/unpeudemath/code/2015/12/15/The-Prisoners-Dilemma-of-Christmas-Gifts/)  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: python71: 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_2015/)  
DONE: compsci1752: 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! (https://www.youtube.com/watch?v=Afyqwc96M1Y)  
DONE: programming  

如果你多运行几次这个程序, 得到的输出结果是不一样的。 这是因为我们调用的协程的同时, 允许其他的HTTP请求执行。 结果最先返回的请求最先打印出来。

总结

尽管Python内置的异步函数使用起来没有Javascript中的那么简便, 不过, 这不意味着它不能使应用更有趣和高效。 花费30分钟去学习异步相关的知识, 你就能更好的把它应用在你的项目中。

原文发布于微信公众号 - 马哥Linux运维(magedu-Linux)

原文发表时间:2016-01-13

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏ytkah

dedecms自增标签[field:global.autoindex/]的运用

  用bootstrap建站时用到幻灯片切换模块,里面有个active(下面代码中的data-slide-to="0"),其余的按顺序递增(1,2),如果用de...

3314
来自专栏CRPER折腾记

Vue折腾记 - (2)写一个不大靠谱的面包屑组件

我把页面标题和面包屑封装到一起..就不用涉及到组件的通讯了, 不然又要去监听路由或者依赖状态去获取

1452
来自专栏码洞

求不更学不动之Redis5.0新特性Stream尝鲜

Redis5.0最近被作者突然放出来了,增加了很多新的特色功能。而Redis5.0最大的新特性就是多出了一个数据结构Stream,它是一个新的强大的支持多播的可...

1696
来自专栏xingoo, 一个梦想做发明家的程序员

图解NodeJS【基于事件、回调的单线程高性能服务器】原理

刚开始了解Node感觉很吊,各种说高性能,可是一直不理解为什么单线程会比多线程快?为什么异步IO比非阻塞IO快?因此,本篇在阅读相关书籍后,根据自己的理解,整...

2037
来自专栏FreeBuf

一个漏洞为何能影响数千万服务器以及66%安卓手机?

安全研究团队Perception Point发现Linux系统内核中存在一个高危级别的本地权限提升0day漏洞,编号为CVE-2016-0728。目前有超过66...

2105
来自专栏喵了个咪的博客空间

phalcon-入门篇6(控制器)

#phalcon-入门篇6(控制器)# ? 本教程基于phalcon2.0.9版本 ##前言## 先在这里感谢各位phalcon技术爱好者,我们提供这样一个优秀...

3156
来自专栏Golang语言社区

深入Go语言网络库的基础实现

Go语言的出现,让我见到了一门语言把网络编程这件事情给做“正确”了,当然,除了Go语言以外,还有很多语言也把这件事情做”正确”了。我一直坚持着这样的理念——要做...

2787
来自专栏海说

6、Java包的命名与划分

包的命名与划分 (一)使用Java包的目的 在了解做一件事之前,需要了解做这件事的目的。而使用Java包的目的大概如下: 1    对类进行归类,便于开发查找。...

3040
来自专栏Java进阶架构师

手把手带你实现JDK动态代理

业务接口Interface、业务实现类target、业务处理类Handler、JVM在内存中生成的动态代理类$Proxy0

812
来自专栏玩转JavaEE

ElementUI中tree控件踩坑记

vhr部门管理模块更新啦!为了让小伙伴们快速理解部门管理模块实现思路,我想通过3篇短文来给大家介绍下大致的实现思路和核心代码。 项目地址:https://git...

4806

扫码关注云+社区

领取腾讯云代金券