Twisted Intro

パート10: 変換された詩

«  パート9: Deferred 再入門   ::   Contents   ::   パート11: 詩が提供されました  »

パート10: 変換された詩

クライアント 5.0

それでは、”パート9: Deferred 再入門” で提案してきたような方向性で、これまで作ってきた詩のクライアントにいくつかの変換ロジックを付け足していきましょう。 しかしまずは、恥ずかしながら告白しておくことがあります。 私は Byronification Engine を記述する術を知りません。 これは私のプログラミングスキルの範疇を超えています。 代わりに、Cummingsifier という、もう少し簡単な変換を実装していくことにしましょう。 Cummingsifier は、詩を受け取って、元の詩に似てはいるものの e.e.cummings 形式で書かれた新しい詩を返すアルゴリズムです。 Cummingsifier アルゴリズムの全てをここに示しましょう。

def cummingsify(poem):
    return poem.lower()

不幸なことに、このアルゴリズムはとても簡単なので決して失敗することがありません。 そこでクライアント 5.0 (twisted-client-5/get-poetry.py にあります) では、次のうちのいずれかを無作為に実行するように cummingsify を少し変更したバージョンを使うことにします。

  1. cummingsified されたバージョンの詩を返します。
  2. GibberishError を送出します。
  3. ValueError を送出します。

このようにして、予期せぬ方法で時々失敗するアルゴリズムをシミュレートします。

クライアント 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()

プログラムがサーバから詩をダウンロードすると、次のいずれかが起こります。

  1. cummingsified バージョン (小文字に変換されたもの) の詩を出力します。
  2. 元々の詩の後に “Cummingsify failed!” と出力します。
  3. “The poem download failed.” と出力します。

私たちは複数のサーバからダウンロードできるようになりましたが、三つの異なる結果を全て見るまで、クライアント 5.0 をテストしてみるときはひとつのサーバを使ってプログラムを何度も実行するだけの方が簡単でしょう。 また、サーバが存在しないポートに対してクライアントを実行させてみてください。

get_poetry から得られたそれぞれの Deferred に付け足したコールバックとエラー用コールバックのチェーンを図にしてみましょう。

_images/p10_deferred-42.png

図19:クライアント 5.0 における遅延オブジェクトの連鎖

addCallback によって付け加えられた何もしないエラー用コールバック (pass-through errback) に注意してください。 これは、受け取った Failure が何であろうとも次のエラー用コールバック (poem_failed) にそれを渡します。 このため poem_failed` は、 get_poetry (つまり、遅延オブジェクトは errback メソッドで発火させられます) と cummingsify 関数の両方から投げられたエラーを扱うことができます。

図19 において Inkscape

cummingsify 関数がきちんと動くのは図20に示します。

_images/p10_deferred-5.png

図20:詩をダウンロードして正確に変換するとき

図21は、詩を受信したものの cummingsifyGibberishError を送出する場合を表します。

_images/p10_deferred-6.png

図21:詩をダウンロードして GibberishError が出るとき

try_to_cummingsify コールバックは GibberishError を再度発生させますので、制御がエラー用コールバックに移り poem_failed は引数として例外を受け取って呼び出されます。(その引数はもちろん Failure でラップされています)

そして poem_failed は 例外を発生させないかあるいは Failure を返しますので、呼び出し終えると制御は通常のコールバックに戻ります。 もしも poem_failed にエラーを完全に処理して欲しければ、 None を返すことが妥当な振る舞いです。 そうではなく poem_failed に何かをやってほしければ、しかしそれでもなおエラーを受け渡しながらであれば、 poem_failed がその err 引数を返し、処理はエラー用コールバックにいくでしょう。

今のコードでは got_poempoem_failed のどちらも失敗しないことに気をつけてください。このため poem_done というエラー用コールバックは決して呼び出されません。 しかし、いかなる場合でもこの段階を踏むことは安全であり、 got_poempoem_failed のどちらかに私たちが知らないバグがあるかもしれませんので、防御的プログラミング (“defensive” programming) を体現します。 addBoth メソッドは、遅延オブジェクトをどのように発火しようとも特定の関数が実行されることを保証しますので、 addBoth を使うことは try/except 文に finally 句を追加することに相当します。

ここでは、詩をダウンロードして cummingsify 関数が ValueError を発生させる場合を検証しましょう。図22に示します。

_images/p10_deferred-7.png

図22:詩をダウンロードして、cummingsify に失敗するとき

got_poem が変換されていない元のバージョンの詩を受け取る、ということを除いて、これは図20と同じです。 この切り替えはすべて try_to_cummingsify コールバックの中で発生します。このコールバックは普通の try/except 文で ValueError を捕まえて、その代わりに元の詩を返します。 遅延オブジェクトがエラーを見ることはありません。

最後に図23で、存在しないサーバから詩をダウンロードしようとした場合を示します。

_images/p10_deferred-8.png

図23:サーバに接続できないとき

前回と同じように、その後の制御が通常のコールバックに戻るように poem_failedNone を返します。

クライアント 5.1

クライアント 5.0 では、遅延オブジェクトに最初に例外を捕まえさせるよりはむしろ、通常の try/except 文を使って try_to_cummingsify コールバックの cummingsify で例外を引っ掛けます。 この戦略に特に悪いところはありませんが、違う方法でどうやってみるかを考えることは勉強になるでしょう。

遅延オブジェクトに GibberishErrorValueError の両方の例外を捕まえさせて、それらをエラー用コールバックの流れに送る場合を考えてみましょう。 現在の振る舞いをそのままにするために、後続するエラー用コールバックはエラーが ValueError かを確認する必要があり、もしそうなら、制御が通常のコールバックの流れに復帰して元の詩が出力されるように、そのままの詩を返すようさせます。

しかし、問題がひとつあります。エラー用コールバックは元の詩を取得できません。 cummingsify 関数から送出され Failure でラップされた ValueError を受け取るのです。 エラー用コールバックにエラーを処理させるために、このコールバックが元の詩を受け取るように工夫する必要があります。

ひとつの方法は、元の詩が例外に含まれるよう cummingsify 関数を変更することです。 これこそがクライアント 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)

私たちが生成したそれぞれの遅延オブジェクトは、図24の構造を持ちます。

_images/p10_deferred-9.png

図24:クライアント 5.1 における遅延オブジェクトの連鎖

cummingsify_failed というエラー用コールバックを確認してください。

def cummingsify_failed(err):
    if err.check(CannotCummingsify):
        print 'Cummingsify failed!'
        return err.value.args[0]
    return err

Failure に内包された例外が CannotCummingsify のインスタンスかを確認するために、 Failure オブジェクトの check メソッドを使っています。 もしもそうなら、第一引数 (元の詩) を例外に返し、エラーを処理します。 戻り値は Failure ではありませんので、制御は通常のコールバックの流れに戻ります。 そうでなければ Failure 自身を返し、エラーを送って (re-raise) エラー用コールバックの流れに落とし込みます。 お分かりのように、例外は Failurevalue 属性で参照できます。

図25は CannotCummingsify 例外を受け取ったときに発生することを表します。

_images/p10_deferred-10.png

図25:CannotCummigsify エラーが発生するとき

ということで、遅延オブジェクトを使うときは、例外を処理するために try/except を使うようにするか、遅延オブジェクトにエラーをエラー用コールバックに再度送らせるかのどちらかを選択できます。

まとめ

パート10では、エラーを誘導してチェーンを辿らせる Deferred の力を使って詩のクライアントを更新しました。 使った例はいくぶん実用的ではありませんが、遅延オブジェクトにおける制御フローがそれぞれのステージの結果によって通常のコールバックとエラー用コールバックを行き来する様子を描き出せていると思います。

さあ、これで遅延オブジェクトに関して知っておくべきことは全て身に着けましたか? いえ、まだです! 今後のパートでも遅延オブジェクトの更なる機能を探求していきましょう。 とはいえ”パート11: 詩が提供されました“ではちょっと趣向を変えて、Twisted を使って詩のサーバを実装するとしましょう。

おすすめの練習問題

  1. 図25は、クライアント 5.1 における遅延オブジェクトが発火する4つの可能性のうちのひとつを表しています。 他の3つを書き出してみてください。

  2. deferred simulator を使って、クライアント 5.0 と 5.1 が発火する様子をシミュレートしてみましょう。 手始めに、このシミュレータプログラムは次のようにして、クライアント 5.0 における try_to_cummingsify 関数が成功する様子を表現できます。

    r poem p
    r None r None
    r None r None

«  パート9: Deferred 再入門   ::   Contents   ::   パート11: 詩が提供されました  »