twisted系列教程十–可以变化的诗

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中,我们用了一个可以随机出现以下结果的算法:

  1. 返回一个正常的结果
  2. 抛出一个GibberishError错误
  3. 抛出一个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上下载一首诗,还会做下面三件事情中的一件:

  1. 打印出cummingsified 版本的诗
  2. 在输出原来的诗后然后打印"Cummingsify failed!"
  3. 打印出“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.

此条目发表在 twisted 分类目录。将固定链接加入收藏夹。

评论功能已关闭。