概述
这篇教程介绍Deferreds,它是Twisted用于控制异步代码执行流程的机制。如果你不知道这意味着什么,不要着急-------这正是我要讲述的。
这篇教程关注那些刚刚要使用Twisted的人们,主要用于帮助理解那些已经用Deferreds实现的代码。
文档假设你有好的python知识。
在学习完此文档后,你能够掌握什么是Deferreds以及如何使用它来协调异步代码。特别地,你将能够:
1.阅读和理解使用Deferreds的代码
2.将同步代码转换为异步代码
3.实现你所希望的对异步代码任意顺序的错误控制
当你写python代码时,一个根深蒂固的观念是如果一系列代码有一个阻塞的部分,那么必须等待阻塞的部分执行完毕才能继续执行。
pod_bay_doors.open() pod.launch()
首先打开bay doors,然后launch.这很好,动作一个接一个执行是编程语言代码执行顺序的内建机制。这很清晰,简洁,并且没有二义性。
异常的存在使情况稍微复杂了些。如果bay_doors.open抛出了一个异常,这样我们就无法知道open是否完成了,能否继续进一步的操作。所以,python给我们提供了try,except,finally和else,这些模型的组合提供了很多方便处理异常的办法,而且工作的很好。
应用函数组合是另一种编写代码执行顺序的方式:
pprint(sorted(x.get_names()))
首先,x.getnames执行,然后sorted用其返回值执行,最后无论sorted返回什么,pprint都打印它。
上面的代码也可以写在下面的形式:
names = x.get_names() sorted_names = sorted(names) pprint(sorted_names)
有时候我们在不需要顺序的时候对顺序进行编码,比如下面的例子:
total = 0 for account in accounts: total += account.get_balance() print("Total balance $%s" % (total,))但是,这也没什么大不了的。
上面所有的这些都工作的很好。所有的都是一件接一件的完成,这些代码之间密不可分。
但是,现在为什么我们要做的不一样呢?
一个假设
如果我们不再依赖前面一行代码执行完成才能开始执行下一行代码。这意味着pod_bay_doors.open直接返回,同时触发一些其它操作来完成open操作,使python解释器直接开始执行pod.launch操作。
这就是说,如果我们不按照python一行一行执行代码的顺序执行,return并不意味着完成。
异步操作?
我们如何保证再执行lanuch时door已经open了?我们如何处理door打开失败的潜在可能?如果open给我们一些何时能launch的关键信息,我们刚如何接收?
更重要的是,既然我们是写代码,我们如何能充分利用上面已经存在的代码?
解决方案的组件
我们仍然需要一种方式来通知“当需要的条件完成时再执行”。
我们需要一种方法区分成功执行或是执行被异常中断, 通常的模型是 try
,expect
,else
,和finally
.
我们需要一种获取执行失败和异常信息的机制,因为需要把这些信息传递给下面需要的代码。
我们需要能够对结果进行操作,我们可以对将要对结果执行的操作进行计划和安排。
除了对解释器进行hack外,我们可以实现上述这些组件,利用python语言现有的构件:方法,函数,对象等等。
可能我们需要的是类似下面这样的代码:
placeholder = pod_bay_doors.open()
placeholder.when_done(pod.launch)
一种解决方案: Deferred
Twisted 用Deferreds来解决这种问题, 一种对象被设计成用来做且仅做一件事:打破python代码现有的执行流,对执行顺序进行重编码。
Deferred并不是用线程,并行,信号或者子进程来实现。它也不关心event loop, greenlets, 或者是如何调用.它只关心以何种顺序运行程序.
那么它是如何做到的?因为我们显式地告诉它我们期望的顺序.
这样的话,下面的代码:
pod_bay_doors.open()
pod.launch()
可以写成下面的样子:
d = pod_bay_doors.open()
d.addCallback(lambda ignored: pod.launch())
通过上面的代码介绍了一些新的概念。我将详细地进行介绍.如果你认为你已经掌握了,可以跳过下一节.
在上面的代码中, pod_bay_doors.open()
返回了一个Deferred,我们将它赋给d
.我们可以把d看作一个占位符, 代码了open()完成时的最终返回结果。
接下来, 我们给d添加了一个callback
。callback是一个函数,它将会在open()最终返回时被调用。在这个例子中,我们并不关心open的返回结果,所以我们使用一个ignored参数调用pod.launch()。
这样,我们就构造出了一种我们期望的执行顺序。
当然,程序通常都不只有两行构成,我们还不知道如何处理失败的情况。
做得更好:处理错误的情况
下面,我们将采取各种方式来表达普通python代码的执行顺序 (使用顺序执行的代码和try
/except
) 并将它们转化为等价的使用Deferred对象的代码。
接下来可能会比较预期, 但是如果你真的想掌握如何使用Deferreds而且打算持续使用它进行编码, 那么你值得理解下面的每一个例子.
一件事, 然后另外一件, 再一件
这是前面的一个例子:
pprint(sorted(x.get_names()))
也可以写成下面这样:
names = x.get_names()
sorted_names = sorted(names)
pprint(sorted_names)
不管是get_names还是sorted,都依赖于在返回之前要操作完成。但是,如果它们都是异步操作该怎么办?
好吧, 在Twisted中,它们可以返回 Deferreds,所以我们可以写成下面这样:
d = x.get_names()
d.addCallback(sorted)
d.addCallback(pprint)
最终,不管get_names最终返回了什么都会调用 sorted
.当sorted
执行完成后,pprint将会以其结果继续执行
.
我们也可以写成下面这样:
x.get_names().addCallback(sorted).addCallback(pprint)
因为 d.addCallback
返回值是d
.
简单的错误处理
我们经常需要写下面这样的代码:
try:
x.get_names()
except Exception, e:
report_error(e)
使用 Deferreds我们又该如何写呢?
d = x.get_names()
d.addErrback(report_error)
errback是Twisted 用来命名接收执行错误的callback 的.
这段代码掩盖了一个重要的细节.不是得到一个异常对象 e
,report_error
得到的是一个Failure对象,它包含了所有e所包含的有用信息, 但是为使用 Deferreds进行了优化.
稍后我会更详细地讨论它, 我们先来讨论所有其它的异常组合.
处理一个错误,并在成功时做另外的操作
如果我们想在try代码块后执行另外一些操作该怎么做?像通常做的那样,我们会这样写:
try:
y = f()
except Exception, e:
g(e)
else:
h(y)
我们重写这段代码使用 Deferreds:
d = f()
d.addCallbacks(h, g)
addCallbacks
意味着同时添加一个callback和一个errback..h
是callback,g
是errback.
现在我们有 addCallbacks
,同时还有
addErrback
和
addCallback
,我们可以用它们来匹配所有
try
,except
,else
,和
finally的可能组合。
.解释这些是如何工作的有些复杂, (尽管Deferred 参考 这篇文章中有很好的讲述), 但是当我们学过了下面的例子原理可能会更清楚一些.
处理一个错误,并继续运行
如果我们想在 try
/except
之后做一些操作,不管是否发生了异常?通常是像下面的代码这样:
try:
y = f()
except Exception, e:
y = g(e)
h(y)
使用 Deferreds:
d = f()
d.addErrback(g)
d.addCallback(h)
因为 addErrback
返回d
, 所以我们可以像下面这样:
f().addErrback(g).addCallback(h)
addErrback
和addCallback的调用顺序是有影响的
.在下面的章节中, 我们将会看到如果调换顺序会发生什么.
为所有的操作进行错误处理
我们想为多个操作进行可能的错误处理?
try:
y = f()
z = h(y)
except Exception, e:
g(e)
使用 Deferreds:
d = f()
d.addCallback(h)
d.addErrback(g)
或者,更简洁的写法:
d = f().addCallback(h).addErrback(g)
无论如何都要执行
考虑finally
?我们想做些操作不管是否发生了异常?代码就像下面这样:
try:
y = f()
finally:
g()
大致上和下面一样:
d = f()
d.addBoth(g)
上面的操作添加了 g
同是作为callback和 errback.和下面的代码等价:
d.addCallbacks(g, g)
为什么说 “大致上”?因为如果 f
抛出了异常,g
将会被传递一个代表这个异常的Failure 对象.否则, 将会传递给g异步代码f的执行结果
(也就是.
y
).
Inline callbacks - 使用 ‘yield’
Twisted 有一个
inlineCallbacks
装饰器,它允许你使用Deferreds而不用编写callback函数.
这是通过把你的代码写成生成器来实现的, 也就是使用yield Deferred
s 来代替关联一个callback函数.
考虑下面标准的Deferred
写法:
def getUsers():
d = makeRequest("GET", "/users")
d.addCallback(json.loads)
return d
使用 inlineCallbacks
, 我们可以写成下面这样:
from twisted.internet.defer import inlineCallbacks, returnValue
@inlineCallbacks
def getUsers(self):
responseBody = yield makeRequest("GET", "/users")
returnValue(json.loads(responseBody))
有2件事情会发生:
- 代替了在返回的Deferred上调用
addCallback
, 我们yield Deferred.这将会使Twisted 给我们返回
Deferred
的结果. - 我们使用
returnValue来传递我们函数的最终结果
。因为这个函数是一个生成器,所以我们不能使用return表达式,那样会导致一个语法错误。
注意
在新的 15.0版本.
在 Python 3.3 和更高的版本中,不用写 returnValue(json.loads(responseBody))
你可以直接写成
returnjson.loads(responseBody)
.这大大增加了可读性, 但是遗憾的是,如果你想与python2兼容,这种写法就不能出现.
下面2个版本的 getUsers
对它们的调用者来说,API都是一样的: 都返回了一个Deferred
,包含了请求的json body.尽管inlineCallbacks
的版本看起来像是同步代码,它会阻塞到直到request返回, 但是yield
表达式允许其它的代码运行,同时当yield的deferred完成会继续被执行。
inlineCallbacks会变得更加强大,尤其是在处理复杂的控制流或者错误处理时。
比如, 如果
makeRequest
因为一个连接错误而失败了?如果遇到了异常,我们想返回一个空的列表.
def getUsers():
d = makeRequest("GET", "/users")
def connectionError(failure):
failure.trap(ConnectionError)
log.failure("makeRequest failed due to connection error",
failure)
return []
d.addCallbacks(json.loads, connectionError)
return d
使用 inlineCallbacks
, 我们可以像下面这样重写:
@inlineCallbacks
def getUsers(self):
try:
responseBody = yield makeRequest("GET", "/users")
except ConnectionError:
log.failure("makeRequest failed due to connection error")
returnValue([])
returnValue(json.loads(responseBody))
我们的异常处理可以更加简单,因为我们可以使用 Python的 try
/except
讲法来处理 ConnectionError
s.
协程使用 async/await
注意
只在Python 3.5 和更高的版本中可用.
在新的版本16.4中.
在 Python 3.5 和更高的版本中, the PEP 492 (“协程使用 async和 await 语法”) “await” 可以和Deferreds 一起使用,通过使用ensureDeferred.它和inlineCallbacks类似
, 除了它使用await关键字而不是
yield
,使用return
关键字来代替 returnValue
, 同时它是一个函数而不是一个生成器.
调用一个协程 (这样的函数定义方式如: asyncdef funcname():
) 使用ensureDeferred 将允许你在Deferreds上使用 “await” , 并且它返回一个标准的Deferred.你可以混合使用通常的 Deferreds,inlineCallbacks
, 和ensureDeferred
.
在一个Deferred上Awaiting,这个Deferred如果失败了将会在你的协程中抛出一个异常,就像通常的python代码中那样。如果你的协程抛出了一个异常, 它将会被转换成Deferred上的一个Failure,然后返回一个ensureDeferred
.调用return将会使Deferred返回一个包含结果的ensureDeferred
.
import json
from twisted.internet.defer import ensureDeferred
from twisted.logger import Logger
log = Logger()
async def getUsers():
try:
return json.loads(await makeRequest("GET", "/users"))
except ConnectionError:
log.failure("makeRequest failed due to connection error")
return []
def do():
d = ensureDeferred(getUsers())
d.addCallback(print)
return d
当你使用协程的时候, 你没有必要使用ensureDeferred .当你写的协程调用了其它在Deferreds上await的协程。你可以直接在上面await,如下所示:
async def foo():
res = await someFunctionThatReturnsADeferred()
return res
async def bar():
baz = await someOtherDeferredFunction()
fooResult = await foo()
return baz + fooResult
def myDeferredReturningFunction():
coro = bar()
return ensureDeferred(coro)
尽管 Deferreds在所有的协程中使用, 只有
bar
需要被用ensureDeferred 装饰来返回一个 Deferred.
总结
已经向你介绍了异步代码和如何使用Deferres:
- 在一个异步操作后如何做来成功的完成。
- 使用一个成功异步操作的结果。
- 在异步操作中捕获异常。
- 在操作成功和失败时各应该如何做。
- 在已经成功地处理了一个错误后继续做一些操作。
- 为多个异步操作设置错误处理。
- 不管异步操作成功还是失败都做一些操作。
- 不使用callbacks,而是使用
inlineCallbacks。
- 使用ensureDeferred与Deferreds交互来使用协程。
这里有一些非常基本的Deferred操作.关于更多Deferred如何工作,如果组合多个Deferreds,如何混合同步和异步API,请查看Deferred参考.另外,了解如何写函数来构造Deferreds.
最后
以上就是坚强歌曲为你收集整理的Twisted学习(三)---------------Deferred介绍的全部内容,希望文章能够帮你解决Twisted学习(三)---------------Deferred介绍所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复