四时宝库

程序员的知识宝库

Twisted Requests:异步requests库trep使用介绍

初学者指南:使用 Python 的 Twisted 包进行异步 API 调用

2020年3月17日 | Moshe Zadka

图片来源:Yuko Honda on Flickr. CC BY-SA 2.0

Twisted Requests(treq)包是一个基于流行的Twisted库构建的HTTP客户端,用于异步请求。异步库提供了并行执行大量网络请求的能力,而且对CPU的影响相对较小。这对于需要在获得所有信息之前进行多次请求的HTTP客户端来说是非常有用的。在这篇文章中,我们将通过一个制作异步调用的例子来探讨如何使用treq。

定义一个要解决的问题

我很喜欢玩一款实时策略游戏,叫做《部落冲突:皇室战争》(Clash Royale)。虽然它不是开源的,但它有一个公开的API,我们可以使用它来展示异步请求是如何派上用场的。

《部落冲突:皇室战争》是一款移动策略玩家对战游戏,玩家可以在一个竞技场中打牌并获胜。每一张牌都有不同的优缺点,玩家们爱用的牌各不相同。《部落冲突:皇室战争》会记住玩家最常用的牌;这是他们“最喜欢的”牌。玩家们以部落的形式聚在一起,互相帮助。《部落冲突:皇室战争》的开发商Supercell发布了一个基于HTTP的API,可以用它来查询不同的统计数据。

这里有一个适用于异步的很好的问题:我们如何编写一个程序来输出一个部落中最受欢迎的纸牌,这样我们就可以开始了解对手(并了解哪些纸牌在我们的部落成员中最受欢迎)?

您可以注册一个帐户来跟随教程一起学习,但即便您不这样做,您还是能够理解我们正在构建的内容。如果你真的要注册一个帐号,通过皇室战争的开发者门户创建一个API令牌,然后在你的资料下选择“创建新密钥”,并输入一个名字、描述和有效的IP地址。(需要一个确切的地址,所以我用这个网站https://whatismyipaddress.com/找到了我的地址)。由于你不应该在你的代码中保存API密钥,所以请将它作为一个单独的文件保存在~/.crtoken中:

Twisted程序

运行一个基于Twisted的程序,需要一些额外的软件包,来使体验尽可能的流畅。我不会在本教程中一一介绍,但每一个都值得我们去探索学习。

为了让大家更容易看懂,我们先从这个打印Hello world的入门程序开始,然后再来说说它的作用:

这里导入的模块比我们在 "Hello world "的例子中所需要的模块要多得多。程序的最终版本将需要这些模块,它将完成异步查询API这一更复杂的任务。导入后,程序从文件中读取token,并将其存储在变量token中。(我们现在不打算对token做什么,但看到这个语法就好了)。接下来有一个接受Twisted反应器主函数。反应器有点像Twisted包的复杂机制的接口。在这段代码中,函数main被作为参数发送给task.react,并给它输入了一个附加参数。

main返回一个defer.success(None)。这就是它返回正确类型的值的方式:一个延迟的值,但是这个值已经被“触发”或“调用”了。因此,程序在打印完Hello world后会立即退出,这也是我们所需要的。

接下来,我们来看看异步函数和ensureDeferred的概念:

在这个程序中,应该是以同样的导入开始,我们把所有的逻辑都移到了异步函数get_clan_details中。就像普通函数一样,异步函数在末尾有一个隐式的返回值None。然而,异步函数,有时也被称为协程,是一种不同于Deferred(延迟)的类型。为了让从Python 1.5.2以来就存在的Twisted使用这个现代特性,我们必须使用sureDeferred来调整协程。

虽然我们可以不使用协程来写所有的逻辑,但使用异步语法可以让我们写出更容易理解的代码,而且我们需要将更少的代码移到嵌入式回调中。

接下来要介绍的概念是await等待)。稍后,我们将等待一个网络调用,但为了简单起见,现在我们将等待一个计时器。Twisted有一个特殊的函数task.deferLater,它将在经过一段时间后调用一个带有给定参数的函数。

以下程序需要5秒钟完成:

关于类型的一个注意事项:task.deferLater会返回一个Deferred值,就像大多数Twisted函数一样,它没有现成可用的值。在运行Twisted事件循环时,我们既可以等待延迟,也可以等待协程。

函数task.deferLater会等待5秒钟,然后调用lambda,计算要打印的字符串。

现在,我们已经拥有了写一个高效的部落分析程序所需的所有Twisted构件!

使用treq进行异步调用

由于我们将使用全局反应器,所以在计算这些统计数据的函数中,我们不再需要接受反应器作为参数:

使用该令牌的方式是作为头文件中的 "承载 "令牌:

我们希望发送部落标签,这些标签将是字符串。部落标签以#开头,所以必须在放入URL之前被转义。这是因为#有 "URL片段 "的特殊含义:

第一步是获取部落的详细信息,包括部落成员:

注意,我们必须等待treq.get调用。因为这是一个异步的网络调用,所以我们必须要明确等待和获取信息的时间。仅仅使用await语法来调用Deferred函数,并不能让我们充分发挥异步的力量(稍后我们将看到如何做到这一点)。

接下来,在获取了头文件之后,我们需要获取内容。treq库给了我们一个辅助方法,可以直接解析JSON:

内容包括了一些关于部落的元数据,还有一个包含部落成员的memberList字段,对于我们目前的目的来说,这些元数据,我们并不感兴趣。注意,虽然它有一些关于玩家的数据,但当前最喜欢的卡牌并不在其中。然而它确实包含了唯一的 "玩家标签",我们不妨用它来检索进一步的数据。

我们收集了所有的玩家标签,由于这些标签也是以#开头的,所以我们将其进行URL转义:

最后,我们来看看treq和Twisted的真正威力:一次性生成所有的玩家数据请求! 这确实可以加速像这样的任务——一次又一次地查询一个API。在API有速率限制的情况下,这可能是个问题。

有些时候,我们需要体贴一下API的拥有者,不触碰任何速率限制。有一些技术可以在Twisted中支持速率限制,但它们不在本教程的范围之内。(一个重要的工具是defer.DeferredSemaphore)。

弦外之音:等待、延迟和回调

对于那些对返回对象的具体内容感到好奇的人来说,下面我们来仔细看看是怎么回事。

请记住,请求不会直接返回JSON体。在此之前,我们使用了 await,这样我们就不用担心请求究竟返回什么。实际上,它们返回的是一个Deferred(延迟值)。一个被延迟的函数可以有一个附加的回调函数来修改Deferred。如果回调返回一个Deferred,那么最终的值就是返回的Deferred的

所以,对于每个Deferred,我们会附加一个回调,回调将检索到主体的JSON:

在延迟中附加回调是一种更手动的技术,这使得代码更难理解,却更有效地利用了异步特性。具体来说,因为我们同时附加了所有的回调,所以我们不需要等待网络调用完成之后再指定如何对结果进行后期处理,而网络调用可能花费很长的时间。

从延迟到值

在收集到所有结果之前,我们无法计算出最受欢迎的卡牌。我们有一个延迟值的列表,但我们想要的是一个得到列表值的延迟值。这种反转正是Twisted的函数defer.gatherResults所做的:

这个看似苍白无力的调用是我们使用Twisted全部力量的地方。defer.gatherResults函数会立即返回一个延迟值,只有当所有组成的延迟实例都被触发时,它才会返回一个延迟并触发相应的结果。它甚至为我们提供了自由的错误处理方式:如果任何一个延迟出错,它将立即返回一个失败的延迟,这将导致await语句抛出异常。

现在我们得到了所有玩家的详细信息,我们需要仔细研究一些数据。我们可以使用Python最酷的内建类之一:collections.Counter。这个类获取一个事物列表,并统计每个事物被看了多少次,这正是我们在计票或人气竞赛中所需要的:

最后,我们把它打印出来:

把它汇总到一起

于是乎,把它汇总在一起,我们得到了:

得益于Twisted和treq的高效和富有表现力的语法,这就是我们对API进行异步调用所需要的全部代码。如果你想知道结果的话,我的部落最喜欢的卡组名单依次是魔法师、巨型骑士、女武神和皇家巨人。

我希望您喜欢使用Twisted来编写更快的API调用!

英文原文:https://opensource.com/article/20/3/treq-python
译者:弹性小胖子

发表评论:

控制面板
您好,欢迎到访网站!
  查看权限
网站分类
最新留言
    友情链接