Twisted学习(三)---------------Deferred介绍

这篇教程介绍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,同时还有addErrbackaddCallback,我们可以用它们来匹配所有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)

 addErrbackaddCallback的调用顺序是有影响的 .在下面的章节中, 我们将会看到如果调换顺序会发生什么.

为所有的操作进行错误处理

我们想为多个操作进行可能的错误处理?

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 Deferreds 来代替关联一个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件事情会发生:

  1. 代替了在返回的Deferred上调用addCallback , 我们yield Deferred.这将会使Twisted 给我们返回Deferred的结果.
  2. 我们使用 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 讲法来处理 ConnectionErrors.

协程使用 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.



©️2020 CSDN 皮肤主题: 终极编程指南 设计师:CSDN官方博客 返回首页