Python Twisted 学习系列7(转载stulife最棒的Twisted入门教程)

第七部分:小插曲,Deferred

作者:[email protected]http://krondo.com/?p=1682译者:杨晓伟(采用意译)

第七部分:小插曲,Deferred

你可以从这里从头开始阅读这个系列

回调函数的后序发展

在第六部分我们认识这样一个情况:回调是Twisted异步编程中的基础。除了与reactor交互外,回调可以安插在任何我们写的Twisted结构内。因此在使用Twisted或其它基于reactor的异步编程体系时,都意味需要将我们的代码组织成一系列由reactor循环可以激活的回调函数链。

即使一个简单的get_poetry函数都需要回调,两个回调函数中一个用于处理正常结果而另一个用于处理错误。作为一个Twisted程序员,我们必须充分利用这一点。应该花点时间思考一下如何更好地使用回调及使用过程中会遇到什么困难。

分析下3.1版本中的get_poetry函数:

...

def got_poem(poem):

print poem

reactor.stop()

def poem_failed(err):

print >>sys.stderr, ‘poem download failed‘

print >>sys.stderr, ‘I am terribly sorry‘

print >>sys.stderr, ‘try again later?‘

reactor.stop()

get_poetry(host, port, got_poem, poem_failed)

reactor.run()

我们想法很简单:

1.如果完成诗歌下载,那么就打印它

2.如果没有下载到诗歌,那就打印出错误信息

3.上面任何一种情况出现,都要停止程序继续运行

同步程序中处理上面的情况会采用如下方式:

...

try:

poem = get_poetry(host, port) # the synchronous version of get_poetry

except Exception, err:

print >>sys.stderr, ‘poem download failed‘

print >>sys.stderr, ‘I am terribly sorry‘

print >>sys.stderr, ‘try again later?‘

sys.exit()

else:

print poem

sys.exit()

即callback类似else处理路径,而errback类似except处理路径。这意味着激活errback回调函数类似于同步程序中抛出一个异常,而激活一个callback意味着同步程序中的正常执行路径。

两个版本有什么不同之外吗?可以明确的是,在同步版本中,Python解释器可以确保只要get_poetry抛出何种类型的异步都会执行except块。即只要我们相信Python解释器能够正确的解释执行Python程序,那么就可以相信异常处理块会在恰当的时间点被执行。

不异步版本相反的是:poem_failed错误回调是由我们自己的代码激活并调用的,即PeotryClientFactory的clientConnectFailed函数。是我们自己而不是Python来确保当出错时错误处理代码能够执行。因此我们必须保证通过调用携带Failure对象的errback来处理任何可能的错误。

否则,我们的程序就会因为等待一个永远不会出现的回调而止步不前。

这里显示出了同步与异步版本的又一个不同之处。如果我们在同步版本中没有使用try/except捕获异步,那么Python解释器会为我们捕获然后关掉我们的程序并打印出错误信息。但是如果我们忘记抛出我们的异步异常(在本程序中是在PoetryClientFactory调用errback),我们的程序会一直运行下去,还开心地以为什么事都没有呢。

显而易见,在异步程序中处理错误是相当重要的,甚至有些严峻。也可以说在异步程序中处理错误信息比处理正常的信息要重要的多,这是因为错误会以多种方式出现,而正确的结果出现的方式是唯一的。当使用Twisted编程时忘记处理异常是一个常犯的错误。

关于上面同步程序代码的另一个默认实事是:else与except块两者只能是运行其中一个(假设我们的get_poetry没有在一个无限循环中运行)。Python解释器不会突然决定两者都运行或突发奇想来运行else块27次。对于通过Python来实现那样的动作是不可能的。

但在异步程序中,我们要负责callback和errback的运行。因此,我们可能就会犯这样的错误:同时调用了callback与errback或激活callback27次。这对于使用get_poetry的用户来说是不幸的。虽然在描述文档中没有明确地说明,像try/except块中的else与except一样,对于每次调用get_poetry时callback与errback只能运行其中一个,不管是我们是否成功得下载完诗歌。

设想一下,我们在调试某个程序时,我们提出了三次诗歌下载请求,但是得到有7次callback被激活和2次errback被激活。可能这时,你会下来检查一下,什么时候get_poetry激活了两次callback并且还抛出一个错误出来。

从另一个视角来看,两个版本都有代码重复。异步的版本中含有两次reactor.stop,同步版本中含有两次sys.exit调用。我们可以重构同步版本如下:

...

try:

poem = get_poetry(host, port) # the synchronous version of get_poetry

except Exception, err:

print >>sys.stderr, ‘poem download failed‘

print >>sys.stderr, ‘I am terribly sorry‘

print >>sys.stderr, ‘try again later?‘

else:

print poem

sys.exit()

我们可以以同样的方式来重构异步版本吗?说实话,确实不太可能,因为callback与errback是两个不同的函数。难道要我们回到使用单一回调来实现重构吗?

好下面是我们在讨论使用回调编程时的一些观点:

1.激活errback是非常重要的。由于errback的功能与except块相同,因此用户需要确保它们的存在。他们并不可选项,而是必选项。

2.不在错误的时间点激活回调与在正确的时间点激活回调同等重要。典型的用法是,callback与errback是互斥的即只能运行其中一个。

3.使用回调函数的代码重构起来有些困难。

来下面的部分,我们还会讨论回调,但是已经可以明白为什么Twisted引入了deferred抽象机制来管理回调了。

Deferred

由于架设在异步程序中大量被使用,并且我们也看到了,正确的使用这一机制需要一些技巧。因此,Twisted开发者设计了一种抽象机制-Deferred-以让程序员在使用回调时更简便。

一个Deferred有一对回调链,一个是为针对正确结果,另一个针对错误结果。新创建的Deferred的这两条链是空的。我们可以向两条链里分别添加callback与errback。其后,就可以用正确的结果或异常来激活Deferred。激活Deferred意味着以我们添加的顺序激活callback或errback。图12展示了一个拥有callback/errback链的Deferred对象:

图12: Deferred

由于defered中不使用reactor,所以我们可以不用在事件循环中使用它。也许你在Deferred中发现一个seTimeout的函数中使用了reactor。放心,它将来将来的版本中删掉。

下面是我们第一人使用deferred的例子twisted-deferred/defer-1.py:

from
twisted.internet.defer import Deferred

def
got_poem(res):

print ‘Your poem is
served:‘

print res

def
poem_failed(err):

print ‘No poetry for you.‘

d =
Deferred()

# add a
callback/errback pair to the chain

d.addCallbacks(got_poem,
poem_failed)

# fire
the chain with a normal result

d.callback(‘This poem is
short.‘)

print
"Finished"

代码开始创建了一个新deferred,然后使用addCallbacks添加了callback/errback对,然后使用callback函数激活了其正常结果处理回调链。当然了,由于只含有一个回调函数还算不上链,但不要紧,运行它:

Your poem is served:
This poem is short.
Finished

有几个问题需要注意:

1.正如3.1版本中我们使用的callback/errback对,添加到deferred中的回调函数只携带一个参数,正确的结果或出错信息。其实,deferred支持回调函数可以有多个参数,但至少得有一个参数并且第一个只能是正确的结果或错误信息。

2.我们向deferred添加的是回调函数对

3.callbac函数携带仅有的一个参数即正确的结果来激活deferred

4.从打印结果顺序可以看出,激活的deferred立即调用了回调。没有任何异步的痕迹。这是因为没有reactor参与导致的。

好了,让我们来试试另外一种情况,twisted-deferred/defer-2.py激活了错误处理回调:

from twisted.internet.defer import Deferred

from twisted.python.failure import Failure

def got_poem(res):

print ‘Your poem is served:‘

print res

def poem_failed(err):

print ‘No poetry for you.‘

d = Deferred()

# add a callback/errback pair to the chain

d.addCallbacks(got_poem, poem_failed)

# fire the chain with an error result

d.errback(Failure(Exception(‘I have failed.‘)))

print "Finished"

运行它打印出的结果为:

No poetry for you.
Finished

激活errback链就调用errback函数而不是callback,并且传进的参数也是错误信息。正如上面那样,errback在deferred激活就被调用。

在前面的例子中,我们将一个Failure对象传给了errback。deferred会将一个Exception对象转换成Failure,因此我们可以这样写:

from twisted.internet.defer import Deferred

def got_poem(res):

print ‘Your poem is served:‘

print res

def poem_failed(err):

print err.__class__

print err

print ‘No poetry for you.‘

d = Deferred()

# add a callback/errback pair to the chain

d.addCallbacks(got_poem, poem_failed)

# fire the chain with an error result

d.errback(Exception(‘I have failed.‘))

运行结果如下:

twisted.python.failure.Failure
[Failure instance: Traceback (failure with no frames): : I have failed.
]
No poetry for you.

这意味着在使用deferred时,我们可以正常地使用Exception。其中deferred会为我们完成向Failure的转换。

下面我们来运行下面的代码看看会出现什么结果:

from twisted.internet.defer import Deferred

def out(s): print s

d = Deferred()

d.addCallbacks(out, out)

d.callback(‘First result‘)

d.callback(‘Second result‘)

print ‘Finished‘

输出结果:

First result
Traceback (most recent call last):
  ...
twisted.internet.defer.AlreadyCalledError

很意外吧,也就是说deferred不允许别人激活它两次。这也就解决了上面出现的那个问题:一个激活会导致多个回调同时出现。而deferred设计机制控制住了这种可能,如果你非要在一个deferred上要激活多个回调,那么正如上面那样,会报异常错。

那deferred能帮助我们重构异步代码吗?考虑下面这个例子:

import sys

from twisted.internet.defer import Deferred

def got_poem(poem):

print poem

from twisted.internet import reactor

reactor.stop()

def poem_failed(err):

print >>sys.stderr, ‘poem download failed‘

print >>sys.stderr, ‘I am terribly sorry‘

print >>sys.stderr, ‘try again later?‘

from twisted.internet import reactor

reactor.stop()

d = Deferred()

d.addCallbacks(got_poem, poem_failed)

from twisted.internet import reactor

reactor.callWhenRunning(d.callback, ‘Another short poem.‘)

reactor.run()

这基本上与我们上面的代码相同,唯一不同的是加进了reactor。我们在启动reactor后调用了callWhenRunning函数来激活deferred。我们利用了callWhenRunning函数可以接收一个额外的参数给回调函数。多数Twisted的API都以这样的方式注册回调函数,包括向deferred添加callback的API。下面我们给deferred回调链添加第二个回调:

import sys

from twisted.internet.defer import Deferred

def got_poem(poem):

print poem

def poem_failed(err):

print >>sys.stderr, ‘poem download failed‘

print >>sys.stderr, ‘I am terribly sorry‘

print >>sys.stderr, ‘try again later?‘

def poem_done(_):

from twisted.internet import reactor

reactor.stop()

d = Deferred()

d.addCallbacks(got_poem, poem_failed)

d.addBoth(poem_done)

from twisted.internet import reactor

reactor.callWhenRunning(d.callback, ‘Another short poem.‘)

reactor.run()

addBoth函数向callback与errback链中添加了相同的回调函数。在这种方式下,deferred有可能也会执行errback链中的回调。这将在下面的部分讨论,只要记住后面我们还会深入讨论deferred。

总结:

在这部分我们分析了回调编程与其中潜藏的问题。我们也认识到了deferred是如何帮我们解决这些问题的:

1.我们不能忽视errback,在任何异步编程的API中都需要它。Deferred支持errbacks。

2.激活回调多次可能会导致很严重的问题。Deferred只能被激活一次,这就类似于同步编程中的try/except的处理方法。

3.含有回调的程序在重构时相当困难。有了deferred,我们就通过修改回调链来重构程序。

关于deferred的故事还没有结束,后面还有大量的细节来讲。但对于使用它来重构我们的客户端已经够用的了,在第八部分将讲述这部分内容。

时间: 2024-10-23 21:46:47

Python Twisted 学习系列7(转载stulife最棒的Twisted入门教程)的相关文章

Python Twisted 学习系列3(转载stulife最棒的Twisted入门教程)

第三部分:初步认识Twisted 作者:[email protected]http://krondo.com/?p=1333译者:杨晓伟(采用意译) 第三部分:开始认识Twisted 可以从这里从头开始阅读这个系列. 用twisted的方式实现前面的内容 最终我们将使用twisted的方式来重新实现我们前面的异步模式客户端.不过,首先我们先稍微写点简单的twisted程序来认识一下twisted. 最最简单的twisted程序就是下面的代码,其在twisted-intro目录中的basic-tw

Python Twisted 学习系列1(转载stulife最棒的Twisted入门教程)

第一部分:Twisted理论基础 作者:[email protected]http://krondo.com/?p=1209译者:杨晓伟(采用意译) 前言: 最近有人在Twisted邮件列表中提出诸如”为任务紧急的人提供一份Twisted介绍”的的需求.值得提前透露的是,这个序列并不会如他们所愿.尤其是介绍Twisted框架和基于Python 的异步编程而言,可能短时间无法讲清楚.因此,如果你时间紧急,这恐怕不是你想找的资料. 我相信如果对异步编程模型一无所知,快速的介绍同样无法让你对其有所理解

Python Twisted 学习系列4(转载stulife最棒的Twisted入门教程)

第四部分:由Twisted支持的诗歌客户端 作者:[email protected]://krondo.com/?p=1445译者:杨晓伟(采用意译) 第四部分:由Twisted支持的诗歌客户端 你可以在这里从头开始阅读这个系列. 第一个twisted支持的诗歌服务器 尽管Twisted大多数情况下用来写服务器代码,为了一开始尽量从简单处着手,我们首先从简单的客户端讲起. 让我们来试试使用Twisted的客户端.源码在twisted-client-1/get-poetry.py.首先像前面一样要

Python Twisted 学习系列20(转载stulife最棒的Twisted入门教程)

第二十部分 轮子中的轮子: Twisted和Erlang 简介 在这个系列中,有一个事实我们还没有介绍,即混合同步的"普通Python"代码与异步Twisted代码不是一个简单的任务,因为在Twisted程序中阻滞不定时间将使异步模型的优势丧失殆尽. 如果你是初次接触异步编程,那么你得到的知识看起来有一些局限.你可以在Twisted框架内使用这些新技术,而不是在更广阔的一般Python代 码世界中.同时,当用Twisted工作时,你仅仅局限于那些专门为作为Twisted程序一部分所写的

Python Twisted 学习系列19(转载stulife最棒的Twisted入门教程)

第十九部分 改变之前的想法 简介 Twisted是一个正在进展的项目,它的开发者会定期添加新的特性并且扩展旧的特性. 随着Twisted 10.1.0发布,开发者向 Deferred 类添加了一个新的特性—— cancellation ——这正是我们今天要研究的. 异步编程将请求和响应解耦了,如此又带来一个新的可能性:在请求结果和返回结果之间,你可能决定不再需要这个结果了.考虑一下 :doc:`p14` 中的诗歌代理服务器.下面是这个如何工作的,至少对于诗歌的第一次请求: 一个对诗歌的请求来了.

Python Twisted 学习系列11(转载stulife最棒的Twisted入门教程)

第十一部分:改进诗歌下载服务器 作者:[email protected]http://krondo.com/?p=2048译者:杨晓伟(采用意译) 第十一部分:改进诗歌下载服务器 你可以从这里从头阅读这个系列. 诗歌下载服务器 到目前为止,我们已经学习了大量关于诗歌下载客户端的Twisted的知识,接下来,我们使用Twisted重新实现我们的服务器端.利益于Twisted的抽象机制,接下来你会发现我们前面已经几乎全部学习到这部分知识了.其实现源码在twisted-server-1/fastpoe

Python Twisted 学习系列22(转载stulife最棒的Twisted入门教程)

第二十二部分 结束 全部完成 呼呼! 感谢你一路支持. 在我开始时完全没有想到这个系列会这样长,会花这么多时间完成,但是创建这个系列的过程使我非常享受,也希望你喜欢它. 既然我已经完成了,我会进一步考虑将其转化为PDF格式.然而,不保证. 最后,我想总结一些帮助你继续学习Twisted的建议. 进一步阅读 首先,我建议阅读Twisted的 在线文档. 虽然它备受指责,但我觉得这总比饱受赞誉要好. 如果你希望使用Twisted进行网络编程, 那么 Jean-Paul Calderone 的广受关注

Python Twisted 学习系列5(转载stulife最棒的Twisted入门教程)

第五部分:由Twisted支持的诗歌客户端 作者:[email protected] http://krondo.com/?p=1522译者:杨晓伟(采用意译) 第五部分:由Twited支持的诗歌下载服务客户端 你可以从这里从头开始阅读这个系列 抽象地构建客户端 在第四部分中,我们构建了第一个使用Twisted的客户端.它确实能很好地工作,但仍有提高的空间. 首先是,这个客户端竟然有创建网络端口并接收端口处的数据这样枯燥的代码.Twisted理应为我们实现这些例程性功能,省得我们每次写一个新的程

Python Twisted 学习系列6(转载stulife最棒的Twisted入门教程)

第六部分:抽象地利用Twisted 作者:[email protected]http://krondo.com/?p=1595译者:杨晓伟(采用意译) 第六部分:抽象地利用Twisted 你可以从这里从头开始阅读这个系列. 打造可以复用的诗歌下载客户端 我们在实现客户端上已经花了大量的工作.最新版本的(2.0)客户端使用了Transports,Protocols和Protocol Factories,即整个Twisted的网络框架.但仍有大的改进空间.2.0版本的客户端只能在命令行里下载诗歌.这

Python Twisted 学习系列8(转载stulife最棒的Twisted入门教程)

第八部分:使用Deferred的诗歌下载客户端 作者:[email protected]http://krondo.com/?p=1778译者:杨晓伟(采用意译) 第八部分:使用Deferred的诗歌下载客户端 可以从这里从头开始阅读这个系列. 客户端4.0 我们已经对deferreds有些理解了,现在我们可以使用它重写我们的客户端.你可以在twisted-client-4/get-poetry.py中看到它的实现. 这里的get_poetry已经再也不需要callback与errback参数了