Part 10: Poetry Transformed
原文:http://krondo.com/blog/?p=1956
作者:dave
译者:notedit
时间:2011.06.22
Client 5.0
现在我们将要想我们的client中加入一些变形逻辑.但是首先我不得不说:我不知道怎样写一个Byronification 引擎,它超出我的能力范围了.做为替代,我会实现一个相对简单的变形–Cummingsifier.Cummingsifier 是可以把一首诗变成令一首cumming风格的诗的算法.下面就是这个算法的实现:
def cummingsify(poem)
return poem.lower()
不幸的是,这个算法很简单以至于很难失败,所以在client 5.0 版本中在 twisted-client-5/get-poetry.py中,我们用了一个可以随机出现以下结果的算法:
- 返回一个正常的结果
- 抛出一个GibberishError错误
- 抛出一个ValueError 错误
通过这种方法我们模拟了一个有时返回异常的复杂的算法.
在client 5.0 中唯一变化的是poetry_main 函数:
def poetry_main():
addresses = parse_args()
from twisted.internet import reactor
poems = []
errors = []
def try_to_cummingsify(poem):
try:
return cummingsify(poem)
except GibberishError:
raise
except:
print 'Cummingsify failed!'
return poem
def got_poem(poem):
print poem
poems.append(poem)
def poem_failed(err):
print >>sys.stderr, 'The poem download failed.'
errors.append(err)
def poem_done(_):
if len(poems) + len(errors) == len(addresses):
reactor.stop()
for address in addresses:
host, port = address
d = get_poetry(host, port)
d.addCallback(try_to_cummingsify)
d.addCallbacks(got_poem, poem_failed)
d.addBoth(poem_done)
reactor.run()
当这个程序从server上下载一首诗,还会做下面三件事情中的一件:
- 打印出cummingsified 版本的诗
- 在输出原来的诗后然后打印"Cummingsify failed!"
- 打印出“The poem download failed.”
尽管我们已经有了从多个server上同时下载诗的能力,但是在测试client 5.0 的时候最好只用一个server,并多运行client 几次,并试着在不开server的时候运行client.
让我们画一下在get_poetry中Deferred的callback/errback 链:

注意pass-through errback,它传递它接收到的任何的Failure到下一个errback(poem_failed).所以poem_failed 可以处理从get_poetry 和cummingsify 传过来的错误.
让我们分析一下我们的deferred 的不同的触发方法.如果我们想要一个正常的正确返回结果,获取诗的流程应该像图片二十:

在这种情况下 没有callback失败,callback一路向下调用.这时候poem_done 会接收到None作为它的结果,因为got_poem 没有返回一个值.假如我们想让后面的callback可以访问到诗的内容,我们可以修改got_poem 并让它明确的返回一首诗.
图片二十一描述了cummingsify 抛出了GibberishError 错误的时候:

因为try_to_cummingsify 抛出了GibberishError 错误,poem_failed 被调用,并接收到Failure 对象.
poem_failed 并没有抛出一个异常,它执行完成后并转向poem_done.我们让poem_failed来处理错误并返回一个None值是一个合理的行为.另一方面,如果我们让poem_failed继续传递这个错误,那poem_done errback 就会被调用.
注意我们这里got_poem 和poem_failed 永远不会失败,所以poem_done errback 永远不会调用.但是加上它是安全的,因为你不知道got_poem 和poem_failed 有没有我们不知道的bug. addBoth 保证了这个函数无论deferred如何被触发都会被调用,addBoth 有点像try/except 中的finally 语句.
现在我们来看一下我们成功下载了一首小诗,但是cummingsify 函数抛出了一个ValueError 的的情况,图片二十二描述了这个过程:

图片二十二跟图片二十是一样的,除了got_poem接收的诗是原来的版本而不是已经转化过的版本.这种变化在try_to_cummingsify callback 中,它用try/except捕捉了ValueError并返回原来版本的诗. deferred 对象一点也感觉不到错误的存在.
最后我们用图片二十三描述一下我们从一个不存在的server上下载一首诗的情况:

根据以前的流程可知,poem_failed 返回None 所以程序流向下一层的poem_done callback.
Client 5.1
在client 5.0 中,我们用了一个普通的try/except 语句来捕捉在try_to_cummingsify callback 中出现的异常,而不是让deferred 首先捕捉它.这种方法本身并没有什么错误,但是我们要知道怎么做才是标准的twisted 范.
让我们假设我们想让deferred 同时捕捉GibberishError 和 ValueError 异常然后把它们交给errback.为了保证能正确处理异常,我们的errback需要去检查错误类型是否是ValueError,假如是的话,返回原来的诗,所以程序流又能返回到callback 并把原来版本的诗打印出来.
但是这里有一个问题:errback 不会得到原来的诗,它只会得到一个Failure 对象.为了让errback能处理这个错误,我们需要做一些变化让errback 能接收到原来的诗.
一个修改的方法就是修改cummingsify 函数,让原来的诗的内容包含在异常中.这个就是我们client 5.1 要完成的功能,代码在 twisted-client-5/get-poetry-1.py.我们把ValueError 变为了一个普通的CannotCummingsify异常,并把原来的诗的内容作为参数.
假如cummingsify 是一个外部模块的函数,那最好的办法就是用另一个可以捕捉除GibberishError之外所有异常的函数包装它,并抛出一个CannotCummingsify 异常.经过这些变化,我们的poetry_main 函数可以变成如下:
def poetry_main():
addresses = parse_args()
from twisted.internet import reactor
poems = []
errors = []
def cummingsify_failed(err):
if err.check(CannotCummingsify):
print 'Cummingsify failed!'
return err.value.args[0]
return err
def got_poem(poem):
print poem
poems.append(poem)
def poem_failed(err):
print >>sys.stderr, 'The poem download failed.'
errors.append(err)
def poem_done(_):
if len(poems) + len(errors) == len(addresses):
reactor.stop()
for address in addresses:
host, port = address
d = get_poetry(host, port)
d.addCallback(cummingsify)
d.addErrback(cummingsify_failed)
d.addCallbacks(got_poem, poem_failed)
d.addBoth(poem_done)
我们的deferred 的callback/errback 链就成下图了,图片二十四:

检查cummingsify_failed errback:
def cummingsify_failed(err):
if err.check(CannotCummingsify):
print 'Cummingsify failed!'
return err.value.args[0]
return err
我们用了check 方法来检测绑定在Failure 对象上的异常是否是CannotCummingsify 的实例.假如是的话,我们返回给异常第一个参数并处理这个错误.因为返回的不再是一个Failure 对象,程序流转向callback执行.否则的话我们返回一个Failure 对象,并传递给下一层的errback.
图片二十五描述了当我们遇到一个CannotCummingsify 异常的时候会发生什么:

所以当我们用deferred 的时候,我们可以选择是用try/except 去处理异常,还是让deferred 把错误再路由出去.
Summary
在第十部分我们用deferred 路由错误的功能来改变我们的client,尽管这个例子不是特别真实的,它确实描述了在一个deferred中怎样根据callback 和errback返回的结果来控制程序的流程.
现在我们已经知道了deferred 的所有的事情了吗?还没有.我们还会继续讲解deferred 在未来的部分.但是我们 先绕个路,在第十一部分,实现我们的twisted poetry server.